\documentclass[oneside]{ctexbook}%基本文档类型是ctex article
% [oneside]选项的作用是，防止\part页面的前后出现空白页，因为书籍类型有内置的设置，就是让part前后生成1个空白页
\input{format.tex}  % 插入格式文件
% ------------------------------------------------------------ %
% 文档开始
% ------------------------------------------------------------ %
\begin{document}
\title{Linux笔记}  % 封面标题
\author{taoyinw@foxmail.com}  % 作者
\date{\today}  % 封面日期
\maketitle  %生成封面

% 双栏目录
\begin{multicols}{2}
    \tableofcontents  %输出目录
\end{multicols}
% 单栏目录
%\tableofcontents  %输出目录

\newpage
\pagestyle{headings}%fancyhdr宏包的内容，headings格式下，页脚是空白，页眉左侧是当前section内容，右侧是页码
\setcounter{page}{1}%从下面开始编页码

% 下面书写正文
% ------------------------------------------------------------ %
\part{安装linux}
\chapter{安装linux}
\section{linux系统的安装 - 分区}
linux至少要创建3个分区，根分区，swap分区和boot分区。(一般情况下是如此)

boot分区，引导启动文件的，一般200MB即可。

swap分区，交换分区，当系统内存不够用的时候，可以用swap分区来充当内存来使用，一般使用物理内存的1.5倍。

根分区就是剩余的分区。

linux默认的浏览器是火狐浏览器

Kdump是一个内核崩溃转存机制

\section{虚拟机的网络连接设置}
有三种形式：
\begin{enumerate}
	\item 桥接模式：优点是通讯畅通。其他人可以寻找到母机，也可以寻找到虚拟机。缺点是，虚拟机会占用一个IP地址号段，如果虚拟机过多，会造成IP地址冲突。
	\item NAT模式：母机会有多个ip地址，母机与虚拟机，通过新的ip地址来与母机之间进行通讯。不占用与母机平行的其他ip地址。外部机器无法通过ip地址寻找到虚拟机，但是虚拟机仍可以访问外网。
	\item 仅主机模式：虚拟机是独立的主机，不能访问外网。
\end{enumerate}

所以，一般情况下，选择NAT模式来使用虚拟机。

\chapter{时间日期类指令}

\paragraph{date指令}

date 显示当前时间

\verb|date +%Y| 显示当前年份

\verb|date +%m| 显示月份

\verb|date +%d| 显示日期

前面的加号不能丢，\verb|Ymd|的大小写是固定的，格式可以自己规定，比如\verb|date +%Y/%m/%d|

同理还有\verb|%H小时|，\verb|%M分钟|，\verb|%S秒钟|

\verb|date -s| 字符串时间 可以设置时间

例，\verb|date -s "2018-10-10 11:22:22"|

\paragraph{cal指令}

语法：\verb|cal [选项]|

cal 显示当前月的日历

\verb|cal 2020| 显示2020年的日历

\chapter{搜索查找类指令}

\paragraph{find指令}

语法格式: \verb|find [搜索范围] [选项]|

\begin{itemize}
    \item \verb|-name <范本样式>|: 指定字符串作为寻找文件或目录的范本样式(可以用通配符星号*)
    \item \verb|-user <拥有者名称>|: 查找符和指定的拥有者名称的文件或目录
    \item \verb|-type <文件类型>|: 只寻找符合指定的文件类型的文件
    \item \verb|-size <文件大小>|: 查找符合指定的文件大小的文件，+20M表示“大于20mb的文件”，这里M一定要大大写。文件大小单元：b —— 块(512字节)，c —— 字节，w —— 字(2字节)，k —— 千字节，M —— 兆字节，G —— 吉字节。
\end{itemize}

\paragraph{locate指令}

快速定位文件路径。

因为之前计算机中已有一个\verb|locate|数据库\verb|/var/lib/locatedb|，已经记录了计算机里所有文件的标识，以便于后面使用时的快速定位。因此，\verb|locate|数据库要经常更新。

\verb|updatedb| 创建/更新\verb|locate|数据库

\paragraph{grep指令}

过滤查找

语法：\verb|grep [选项] 查找内容 源文件| 

例: \verb/ cat hello.txt | grep yes /(cat指令浏览hello.txt文件，把浏览的结果交给grep指令去查找)

\verb|-n| 显示出行号

默认是区分大小写的

\verb|-i| 不区分大小写

\chapter{deb包离线安装}

下载地址\verb|pkgs.org|

选择符合自己操作系统版本的deb包

安装的命令\verb|dpkg -i package_name|

\part{基本内容}
\chapter{linux的文件系统}
\section{linux系统的目录结构}
\subsubsection{linux与windows的目录结构的区别}
windows的目录结构:\verb|C:|\quad\verb|C:/**/**|
linux的目录结构:\verb|/| \quad 根目录。根目录下面有很多的子目录，比如说\verb|/bin|，\verb|/home|等。但是根目录只有一个。

级层式的树状目录结构。在此结构中的最上层是根目录\verb|/|，然后在此目录下再创建其他的目录。

"在linux的世界里，一切皆为文件"

例如\verb|/dev| 管理设备的(比如cpu，硬盘等，会被映射到\verb|/dev|目录下的某一个文件来进行管理)

基本目录名称:

\begin{figure}[h]
\centering
\begin{tabular}{|l|p{40em}|}
\hline
\verb|/boot|& 启动linux时使用的核心文件 \\ \hline
\verb|/root|& root用户的文件\\ \hline
\verb|/bin|& 常用的指令\\ \hline
\verb|/sbin| &超级用户的指令\\ \hline
\verb|/dev| &管理设备的\\ \hline
\verb|/etc| &所有的系统管理所需要的配置文件和子目录。\\ \hline
\verb|/home| &存放普通用户的主目录，新建一个用户，那么在/home目录下就会新建一个对应的目录。\\ \hline
\verb|/var| &变量\\ \hline
\verb|/lib |&动态库，类似win里面的dll文件，几乎所有的应用程序都需要用到这些共享库。\\ \hline
\verb|/lib64| &也是库文件\\ \hline
\verb|/usr| &用户安装的文件程序等，类似于win里面的program files目录\\ \hline
\verb|/media| &u盘，光驱等\\ \hline
\verb|/mnt| &系统提供该目录是为了让用户临时挂载别的文件系统的，我们可以将外部的存储关在在\verb|/mnt|上，然后进入该目录就可以查看里面的内容了。\\ \hline
\verb|/opt| &额外安装的可选应用程序包所放置的位置\\ \hline
\verb|/usr/local| &安装的文件所在地(不是安装包，而是安装的东西所在的目录)\\ \hline
\verb|/proc| &内核的相关东西(不要擅自改动)\\ \hline
\verb|/sys| &系统(不要擅自改动)\\ \hline
\verb|/srv| &服务(不要擅自改动)\\ \hline
\verb|/selinux| &安全目录(security enhanced linux)\\ \hline
\verb|/tmp| &临时文件夹\\ \hline
\end{tabular}
\end{figure}

\section{文件的权限}

\subsection{文件基本属性}

文件基本属性: 1文件属性2文件数3拥有者4所属组5文件大小6建档日期7文件名

ls -l命令下，第一列显示的就是文件的属性。

\begin{enumerate}
\item 第1位的意义：
\begin{itemize}
\item 当为[ d ]则是目录
\item 当为[ - ]则是文件；
\item 若是[ l ]则表示为链接文档(link file)；
\item 若是[ b ]则表示为装置文件里面的可供储存的接口设备(可随机存取装置)；
\item 若是[ c ]则表示为装置文件里面的串行端口设备，例如键盘、鼠标(一次性读取装置)
\end{itemize}
\item 2-4位的意义：且均为『rwx』 的三个参数的组合。其中，[ r ]代表可读(read)、[ w ]代表可写(write)、[ x ]代表可执行(execute)，这三个权限的位置不会改变，如果没有权限，就会出现减号[ - ]而已
\item 5-7位的意义：第5-7位确定属组(所有者的同组用户)拥有该文件的权限
\item 8-10位的意义：第8-10位确定其他用户拥有该文件的权限
\end{enumerate}

“文件大小”，如果该文件是目录，则显示4096

\paragraph{文件的rwx权限}

\begin{itemize}
\item r代表可读
\item w代表可写，可以修改，但是不代表可以删除该文件。删除一个文件的前提是对该文件所在的目录有w权限，才能删除位于该文件目录下的某一个文件。
\item x代表可执行
\end{itemize}

\paragraph{目录的rwx权限}

\begin{itemize}
\item r代表可读，可以用ls指令查看目录内容
\item w代表可写，即可修改，可以在目录内创建、删除、重命名等。
\item x代表可执行，即可以进入该目录。
\item 一个点.代表当前目录，两个点..代表上一级目录。
\end{itemize}

\subsection{修改文件权限 - chmod命令}

修改权限\verb|chmod|，可以修改文件或者目录的权限。

语法：
\verb|chmod u=rwx,g=rx,o=x |文件/目录名

\verb|chmod o+w filename|为其他人增加写权限

\verb|chmod a=x filename|为所有人增加可执行权限

也可以用数组来表述权限，\verb|r=4, w=2, x=1|，用他们的和来表示对于该分组的权限，而连续使用3个数字，来分别表示\verb|u|、\verb|g|和\verb|o|用户对该文件的权限

\verb|chmod 777 filename|则等同于\verb|chmod a=rwx filename|

\verb|chmod 771 filename|等同于\verb|chmod ug=rwx, o=x filename|

\paragraph{用户的分类}

\verb|u|文件的所有者

\verb|g|与所有者处于同一分组的其他用户

\verb|o|其他以外的所有用户

\verb|a|所有用户，以上三者均包括

\subsection{文件的组管理}

文件有"所有者"、"所在组"、"其他组"等等标识。其他组指的就是除去文件的所在组剩下的其他的组。

\paragraph{查看文件的所有者}

\verb|ls -ahl|

一般来说，文件所在组就是文件所有者所在的组，但是也可以更改。

\paragraph{修改文件所有者}

chown 用户名 文件名

-R 递归生效

\paragraph{修改文件所在组}

\verb|chgrp 组名 文件名|

\paragraph{改变用户所在组}

\verb|usermod -g 组名 用户名|

\verb|usermod -d 目录名 用户名| 改变用户登录的初始目录

\section{压缩与解压缩}
\paragraph{tar}

\verb|tar -C {目录}| 解压到指定目录

\verb|-f| 指定压缩包文件的名称，\verb|f|选项必须放在所有选项的后面，必须写作\verb|-xf|或者\verb|-jcvf|。

\verb|-x| 解压

\verb|-z| 压缩成\verb|*.gz|压缩文件

\verb|-j|压缩成\verb|*.bz2|压缩文件

\verb|-c| 创建压缩文件

\subparagraph{使用示例}

\verb|tar -zcvf test.tar.gz {文件名}| 把某文件压缩成一个压缩文件，名为\verb|test.tar.gz|

\verb|tar -zxvf test.tar.gz|解压压缩文件\verb|test.tar.gz|

\paragraph{gzip/gunzip指令}

\verb|gzip|作用是压缩文件，特点是压缩完把源文件删除。

格式：\verb|gzip [OPTIONS]... [FILE]...|

选项：

\begin{table}[ht]
    \centering
    \caption{gzip命令常用选项} \label{gzip命令常用选项}
    \begin{tabular}{|l|l|}
        \hline
        基础指令                               &              \\ \hline
        \verb|-d, --decompress, --uncompress| & 解压压缩文件       \\ \hline
        \verb|-l, --list|                     & 列出压缩文件的相关信息  \\ \hline
        \verb|-v, --verbose|                  & 显示指令执行过程     \\ \hline
        \verb|-t, --test|                     & 测试压缩文件是否正确无误  \\ \hline
    \end{tabular}
\end{table}

\verb|gunzip|作用是解压文件

事实上 \verb|gunzip| 就是 \verb|gzip| 的硬连接，因此不论是压缩或解压缩，都可通过 \verb|gzip| 指令单独完成。

\paragraph{zip指令}

语法：\verb|zip [选项] [参数]|

\verb|-r|：递归处理，将指定目录下的所有文件和子目录一并处理

\paragraph{unzip指令}

语法：\verb|unzip [选项] [参数]|

\verb|-d <目录>|：指定文件解压缩后所要存储的目录

\paragraph{为什么linux里的压缩文件尝尝为*.tar.gz，而非*.zip等}

因为tar是一个将多个文件打包成一个文件的程序，他本身是不包含压缩的功能的。

而gz，来自于gzip压缩程序，将一个*.tar文件进行压缩。gzip是在linux系统中广泛采用的一个开源的压缩程序。

\section{文件目录类指令}
\subsection{pwd指令}
\verb|pwd|指令 显示当前目录的绝对路径

\subsection{ls指令}
\verb|ls [选项] [目录或者文件]|

\verb|-a| 显示当前目录所有的文件和目录，包括隐藏的，而且包括\verb|.|和\verb|..|

\verb|-A|显示隐藏文件，但是不包括\verb|.|和\verb|..|

\verb|-l| 以列表的方式显示信息

这两个选项一起用的时候，直接输入-al即可，没必要写成-l -a的形式

\verb|-R|把子目录的文件也一并显示出来

\subsection{cd指令}
\verb|cd ~| 回到家目录

\verb|cd ..| 回到上一级目录

绝对路径，从根目录开始定位

相对路径，形如\verb|../home|这样的路径，从当前工作目录开始定位到需要的路径去

\subsection{mkdir、rmdir、rm}
\verb|mkdir [选项]| 要创建的目录

\verb|-p| 可以一次性创建多级目录

\verb|rmdir| 删除目录

\verb|rmdir [选项]| 要删除的目录

如果要删除非空目录，要用\verb|rm -rf 目录|才行

\subsection{touch}
touch命令用于修改文件或者目录的时间属性，包括存取时间和更改时间。若文件不存在，系统会建立一个新的文件。

语法:\verb|touch [-acfm][-d<日期时间>][-r<参考文件或目录>] [-t<日期时间>][--help][--version][文件或目录…]|

\begin{table}[h]
    \centering
    \caption{touch指令的选项}
    \begin{tabular}{{|l|p{33em}|}}
        \hline
        a  &  改变档案的读取时间记录。 \\ \hline
        m  &  改变档案的修改时间记录。 \\ \hline
        c  &  假如目的档案不存在，不会建立新的档案。与 --no-create 的效果一样。 \\ \hline
        f  &  不使用，是为了与其他 unix 系统的相容性而保留。 \\ \hline
        r  &  使用参考档的时间记录，与 --file 的效果一样。 \\ \hline
        d  &  设定时间与日期，可以使用各种不同的格式。 \\ \hline
        t  &  设定档案的时间记录，格式与 date 指令相同。 \\ \hline
        --no-create  &  不会建立新档案。 \\ \hline
        --help  &  列出指令格式。 \\ \hline
        --version  &  列出版本讯息。 \\ \hline
    \end{tabular}
\end{table}

\subsection{rsync}
\verb|rsync|指令，可以用来同步两个文件夹下的所有内容。既可以同步本机内的两个路径，也可以同步主机和远端主机的路径。

\paragraph{基本用法}
\begin{lstlisting}
rsync -r [source] [destination]
\end{lstlisting}

\paragraph{指令选项}
\verb|-r|选项是必须的选项，表示递归，同步的时候包含子目录

\verb|-a|选项，可以替代-r，除了可以递归同步以外，还可以同步元信息(比如修改时间、权限等)。由于 \verb|rsync| 默认使用文件大小和修改时间决定文件是否需要更新，所以大多是时候\verb|-a|比\verb|-r|更有用。

\verb|-n|，模拟命令执行的结果，并非是真的执行该命令。

\verb|-v|，将结果输出到终端，观察调试信息。

\verb|--delete|选项。

默认情况下，\verb|rsync| 只确保源目录的所有内容(明确排除的文件除外)都复制到目标目录。它不会使两个目录保持相同，并且不会删除文件。如果要使得目标目录成为源目录的镜像副本，则必须使用\verb|--delete|参数，这将删除只存在于目标目录、不存在于源目录的文件。

\verb|--exclude|选项，可以排除一些文件。
\begin{lstlisting}
rsync -av --exclude='*.txt' source/ destination
rsync -av --exclude '*.txt' source/ destination  # 使用等于号或者空格均可
rsync -av --exclude 'file1.txt' --exclude 'dir1/*' source/ destination  # 如果要排除多个文件，要多次使用--exclude参数
\end{lstlisting}

\paragraph{文件夹名后面的斜线}
如果不加上斜线，源目录\verb|source|被完整地复制到了目标目录\verb|destination|下面，即形成了\verb|destination/source|的目录结构。

如果加上了斜线，则会将\verb|source|里面的内容，都同步到\verb|destination|里面，而不是在\verb|destination|下面创建1个新的\verb|source|文件夹。
\begin{lstlisting}
rsync -a source/ destination
\end{lstlisting}

\paragraph{远程同步}
\verb|rsync|命令，还支持通过ssh协议来进行远程同步。远端主机的路径，使用\verb|username@remote_address:[filepath]|的格式给出。

\verb|rsync|命令默认使用ssh协议。\verb|-e|选项可以指定协议，也可以给出一些ssh所需的参数。由于是默认使用ssh协议的，所以\verb|-e|选项很少使用。

\paragraph{增量备份}
\verb|rsync|命令，支持增量备份功能，也就是，只拷贝发生了变化的文件。

除了源目录与目标目录直接比较，\verb|rsync| 还支持使用基准目录，即将源目录与基准目录之间变动的部分，同步到目标目录。

具体做法是，第一次同步是全量备份，所有文件在基准目录里面同步一份。以后每一次同步都是增量备份，只同步源目录与基准目录之间有变动的部分，将这部分保存在一个新的目标目录。这个新的目标目录之中，也是包含所有文件，但实际上，只有那些变动过的文件是存在于该目录，其他没有变动的文件都是指向基准目录文件的硬链接。

\verb|--link-dest|参数用来指定同步时的基准目录。
\begin{lstlisting}
rsync -a --delete --link-dest /compare/path /source/path /target/path
\end{lstlisting}

上面命令中，\verb|--link-dest|参数指定基准目录\verb|/compare/path|，然后源目录\verb|/source/path|跟基准目录进行比较，找出变动的文件，将它们拷贝到目标目录\verb|/target/path|。那些没变动的文件则会生成硬链接。这个命令的第一次备份时是全量备份，后面就都是增量备份了。

\paragraph{rsync、scp、cp 与 rcp 的区别}
表\ref{rsync、scp、cp 与 rcp 的区别}列举了\verb|rsync|和其他几个与复制有关的命令的区别
\begin{table}[!ht]
    \centering
    \caption{rsync、scp、cp 与 rcp 的区别}\label{rsync、scp、cp 与 rcp 的区别}
    \begin{tabular}{|l|p{9em}|p{9em}|p{9em}|p{9em}|}
        \hline
        特性    & rsync            & scp                 & cp          & rcp       \\ \hline
        增量传输  & 仅传输变更部分          & 全量复制所有数据            & 完整复制所有数据    & 全量复制所有数据  \\ \hline
        断点续传  & 支持               & 不支持，需重新开始           & 不支持         & 不支持       \\ \hline
        远程传输  & 支持本地和远程传输        & 仅支持远程传输             & 仅支持本地复制     & 仅支持远程传输   \\ \hline
        数据压缩  & 支持压缩传输           & 不支持                 & 不支持         & 不支持       \\ \hline
        文件权限  & 保留文件权限和时间戳       & 保留部分权限，但不如 rsync 完整 & 保留文件权限      & 保留文件权限    \\ \hline
        可用协议  & 支持 rsync 协议和 SSH & 仅使用 SSH             & 仅本地操作       & 使用 rcp 协议 \\ \hline
        使用方便性 & 可以通过丰富的参数灵活控制    & 语法简单，使用方便           & 语法简单，使用方便   & 语法简单，使用方便 \\ \hline
        适用场景  & 适合增量备份和大规模同步     & 适合快速复制小文件或文件夹       & 适合简单的本地文件复制 & 适合远程文件复制  \\ \hline
    \end{tabular}
\end{table}


\section{IO功能，管道符号}
\paragraph{管道符号|}

表示将前一个命令的结果输出给后面一个命令去处理

\paragraph{输出重定向指令>}
会将原来的文件覆盖

\verb|ls -l > a.txt| 列表的内容写入文件a.txt中(覆盖写入)

\verb|cat 文件1 > 文件2| 将文件1的内容覆盖到文件2

\paragraph{追加指令>>}
不会覆盖原来的文件

\verb|ls -al >> aa.txt| 列表的内容追加到文件aa.txt的末尾

\verb|echo "内容">>文件1| 把"内容"追加到文件1中

\chapter{进程、线程与服务}
\subsection{进程与服务}
linux中，每个执行的程序(代码)都称为一个进程。每一个进程都分配一个ID号。

每一个进程，都会对应一个父进程，而这个父进程可以复制多个子进程。例如www服务器。

每个进程都可能以两种方式存在的，\textbf{前台}和\textbf{后台}。所谓前台进程就是用户目前屏幕上可以进行操作的。后台进程则是实际在操作，但由于屏幕上无法看到的进程，通常使用后台方式执行。

一般系统的服务都是以后台进程的方式存在，而且都会常驻在系统中，直到关机才结束。

linux最大的进程是\verb|init|

\paragraph{ps指令}

\verb|ps|指令: 用来查看目前系统中，有哪些正在执行的进程，以及它们执行的状况。可以不加任何参数。

\verb|-a| 显示当前终端所有进程信息

\verb|-u| 以用户的格式显示进程信息

\verb|-s| 显示后台进程运行的参数

\verb|l| ：较长、较详细的将该PID 的的信息列出

\verb|j| ：工作的格式 (jobs format)

\verb|-f| ：做一个更为完整的输出

输入\verb|ps -ef | more|是以全格式形式显示，就能看见父进程

\verb|ps -aus|指令显示界面的意义

\begin{figure}[h]
\centering
\begin{tabular}{{|l|p{40em}|}}
\hline
\verb|%CPU|   & 占用CPU的情况\\ \hline
\verb|%MEM|   & 占用内存的情况\\ \hline
\verb|VSZ|    & 使用的虚拟内存\\ \hline
\verb|RSS|    & 使用的物理内存的情况\\ \hline
\verb|TTY|    & 使用的终端的情况\\ \hline
\verb|STAT|   & 代表进程当前的状态，S代表休眠sleep，r代表正在运行run\\ \hline
\verb|START|  & 进程的启动时间\\ \hline
\verb|TIME|   & 占用CPU的总时间\\ \hline
\verb|COMMAND|& 进程在启动时的命令行，如果过长会被截断显示\\ \hline
\end{tabular}
\end{figure}

\verb|kill|指令:终止进程	

\verb|killall|指令

\verb|pstree|指令

\verb|-p|：显示程序识别码，即UID

\verb|-u|：显示用户名称

服务，本质上就是后台运行的进程

守护进程

\verb|chkconfig|命令用于检查，设置系统的各种服务

\verb|chkconfig [--add][--del][--list][系统服务]| 

或者\verb|chkconfig [--level <等级代号>][系统服务][on/off/reset]|

\begin{figure}
\centering
\begin{tabular}{{|l|p{33em}|}}
\hline
\verb|--add |　&增加所指定的系统服务，让chkconfig指令得以管理它，并同时在系统启动的叙述文件内增加相关数据\\ \hline
\verb|--del| 　& 删除所指定的系统服务，不再由chkconfig指令管理，并同时在系统启动的叙述文件内删除相关数据\\ \hline
\verb|--level<等级代号>| 　&指定读系统服务要在哪一个执行等级中开启或关毕\\ \hline
\verb|-list| &列出chkconfig所知道的所有命令\\ \hline
\end{tabular}
\end{figure}

\verb|top|命令，top与ps不同的是，运行一段时间后会更新当前进程的情况

\verb|netstat|命令，查看系统网络情况

\paragraph{开机流程}

开机 -> BIOS -> /boot -> init进程1 -> 运行级别 -> 运行级对应的服务

\verb|chkconfig [服务名] --list|

如果写了[服务名]就可以单独查询某个服务的运行级别明细

\verb|chkconfig --level 5 [服务名] on/off|：设定某个服务在运行级别5的条件下是否自启动



\subsection{任务调度}

任务调度，是指系统在某个时间执行特定的命令或程序。

\paragraph{crontab指令}

语法：\verb|crontab [选项] [参数]|

\begin{itemize}
\item -e：编辑该用户的计时器设置
\item -l：列出该用户的计时器设置
\item -r：删除该用户的计时器设置
\item -u<用户名称>：指定要设定计时器的用户名称
\end{itemize}

\subsection{动态监控进程}

\paragraph{top指令}

top与ps不同的是，运行一段时间后会更新当前进程的情况

\begin{table}[h]
\centering
\caption{top指令的参数}
\begin{tabular}{{|l|p{33em}|}}
\hline
-b & 以批处理模式操作 \\ \hline
-c & 显示完整的治命令 \\ \hline
-d & 屏幕刷新间隔时间(默认是3秒) \\ \hline
-I & 忽略失效过程 \\ \hline
-s & 保密模式 \\ \hline
-S & 累积模式 \\ \hline
-i<时间> & 设置间隔时间 \\ \hline
-u<用户名> & 指定用户名 \\ \hline
-p<进程号> & 指定进程 \\ \hline
-n<次数> & 循环显示的次数 \\ \hline
\end{tabular}
\end{table}

\paragraph{top指令的交互命令}

在top命令执行过程中可以使用的一些交互命令。这些命令都是单字母的。

如果在命令行中使用了-s选项， 其中一些命令可能会被屏蔽。

\begin{table}[h]
\centering
\caption{top指令的交互命令}
\begin{tabular}{{|l|p{33em}|}}
\hline
h & 显示帮助画面，给出一些简短的命令总结说明 \\ \hline
k & 终止一个进程 \\ \hline
i & 忽略闲置和僵死进程，这是一个开关式命令 \\ \hline
q & 退出程序 \\ \hline
r & 重新安排一个进程的优先级别 \\ \hline
S & 切换到累计模式 \\ \hline
s & 改变两次刷新之间的延迟时间(单位为s)，如果有小数，就换算成ms。输入0值则系统将不断刷新，默认值是5s \\ \hline
f或者F & 从当前显示中添加或者删除项目 \\ \hline
o或者O & 改变显示项目的顺序 \\ \hline
l & 切换显示平均负载和启动时间信息 \\ \hline
m & 切换显示内存信息 \\ \hline
t & 切换显示进程和CPU状态信息 \\ \hline
c & 切换显示命令名称和完整命令行 \\ \hline
M & 根据驻留内存大小进行排序 \\ \hline
P & 根据CPU使用百分比大小进行排序 \\ \hline
T & 根据时间/累计时间进行排序 \\ \hline
w & 将当前设置写入~/.toprc文件中 \\ \hline
\end{tabular}
\end{table}

\paragraph{top指令能显示的东西}

第一行：当前时间，开始至今时间，用户数，负载

进程情况

cpu情况

内存使用情况

\paragraph{netstat指令}

查看系统网络情况

\chapter{用户管理，用户组}

\section{用户、用户组管理}

linux是一个多用户多进程的系统，任何想使用系统资源的人都必须向系统管理员申请一个账号，然后用这个账号的身份进入系统。

尽量少用root账号登录

在提示符下输入\verb|logout|即可注销用户

\verb|logout|在图形运行级别下无效，在运行级别3下有效

linux的用户至少要属于一个组，可以同时属于多个组。

\paragraph{创建用户}

\verb|useradd|命令可以添加用户

当创建用户成功后，会自动创建和用户同名的\verb|home|目录

也可以通过\verb|useradd -d 新目录 新用户名|，给新创建的用户指定其他的home目录

给用户指定/修改密码: \verb|passwd 用户名|

\paragraph{删除用户}

\verb|userdel [选项] 用户名|

\verb|-r |删除用户对应的home目录，但是一般来说，会保留home目录。

\paragraph{查询用户信息}

\verb|id 用户名|，如果用户不存在的时候，返回无此用户。一般来说会返回uid(用户id号)，gid(用户所在组的id号)，还有组的名称。

\paragraph{切换用户}

\verb|su - 用户名|

高权限用户切换到低权限用户时，不需要输入密码。低权限用户并不能切换到高权限用户。

查询当前用户\verb|whami|

\paragraph{用户组}

系统可以对有共性的多个用户进行统一的管理。

创建组: \verb|groupadd 组名|

删除组: \verb|groupdel 组名|

给用户指定组: \verb|useradd -g 用户组 用户名|

修改用户的组: \verb|usermod -g 要改到的用户组 用户名|

\paragraph{用户的配置文件}

\verb|/etc/passwd| 用户的配置文件，记录用户的各种信息

每行的含义：用户名：口令：用户标识号：组标识号：注释性描述：主目录：登录shell

这里的密码是加密的，不会显示出来。

\verb|/etc/shadow| 口令配置文件，存放密码和登录信息，但是是加密的。

每行的含义：登录名：加密口令：最后一次修改时间：最小时间间隔：最大时间间隔：警告时间：不活动时间：失效时间：标志

\verb|/etc/group| 组配置文件，存放组的信息

每行的含义：组名：口令：组标识号：组内用户列表

\section{linux的运行级别}

\paragraph{运行级别}

\begin{itemize}
    \item 0关机
    \item 1单用户
    \item 2多用户(无网络服务)
    \item 3多用户(有网络服务)
    \item 4保留(unused，linux还没启用过这一级别)
    \item 5图形界面
    \item 6重启
\end{itemize}

系统的运行级别的配置文件/etc/inittab

\paragraph{切换运行级别}

\verb|init [0123456]|




\chapter{linux硬件管理}
\section{linux的硬盘管理}
\subsection{磁盘分区}
\subsubsection{两种分区模式}
分区有2种模式。mbr分区和gtp分区。

mbr分区：
\begin{enumerate}
	\item 最多支持4个主分区
	\item 系统只能安装在主分区
	\item 扩展分区要占一个主分区
	\item MBR最大只支持2TB，但是拥有最好的兼容性
\end{enumerate}

gtp分区
\begin{enumerate}
	\item 支持无限多个主分区(但是操作系统可能限制，比如windows下最多128个分区)
	\item 最大支持18EB的大容量(1EB=1028PB 1PB=1024TB)
	\item windows7 64位以后支持gtp
\end{enumerate}

linux无论有几个分区，分给哪一个目录使用，它归根结底就只有一个根目录，一个独立且唯一的文件结构，linux中每个分区都是用来组成整个文件系统的一部分。

linux采用了一种叫做“载入”的处理方法，它的整个文件系统中包含了一整套的文件和目录，而且将一个分区和一个目录联系起来。这时要载入的一个分区将使它的存储空间在一个目录下获得。

\subsubsection{IDE硬盘与SCSI硬盘}
linux硬盘分为IDE硬盘(并口)和SCSI硬盘(串口)，目前基本上是SCSI硬盘。

如果是IDE硬盘则标识为hdxn。其中，hd标识分区所在的设备的类型，这里指的就是IDE硬盘。x为盘号(a为基本盘，b为基本从属盘，c为辅助主盘，d为辅助从属盘)，n代表分区。前4个分区分别用数字1234表示，他们是主分区或扩展分区，从5开始就是逻辑分区。例如，hda3就表示为第一个IDE硬盘上的第三个主分区或扩展分区，hdb2表示为第二个IDE硬盘上的第二个主分区或扩展分区。

对于SCSI硬盘则标识为sd，其余x和n的含义与IDE硬盘一样。

\verb|lsblk|指令，查看分区挂载情况

\subsection{磁盘情况查询}
\subsubsection{df命令}
\verb|df|命令用于显示磁盘分区上的可使用的磁盘空间。默认显示单位为KB。可以利用该命令来获取硬盘被占用了多少空间，目前还剩下多少空间等信息。

\begin{figure}[h]
    \centering
    \begin{tabular}{|l|l|}
        \hline
        \verb|-h|或\verb|--human-readable| & 以可读性较高的方式来显示信息，不再全部换算为KB为单位 \\ \hline
        \verb|-l|或\verb|--local| & 仅显示本地端的文件系统 \\ \hline
    \end{tabular}
\end{figure}

\subsubsection{du命令}
\verb|du|指令

\begin{figure}[h]
    \centering
    \begin{tabular}{|l|l|}
        \hline
        \verb|-a|或\verb|--all| & 显示所有文件和目录的磁盘使用情况，包括默认被忽略的文件 \\ \hline
        \verb|-c|或\verb|--total| & 在最后打印所有指定路径的总和 \\ \hline
        \verb|-h|或\verb|--human-readable| & 以可读性较高的方式来显示信息，不再全部换算为KB为单位 \\ \hline
        \verb|-d|或\verb|--max-depth| & 设置递归的最大深度。0表示只显示当前目录的直接内容，不进行递归。 \\ \hline
        \verb|-s|或\verb|--summerize| & 只显示总计(所占磁盘空间)，忽略子目录 \\ \hline
    \end{tabular}
\end{figure}

\paragraph{查看目录下文件、子目录的个数}
统计\verb|/home|目录下文件的个数
\begin{lstlisting}
ls -l /home | grep "^-" | wc -l
\end{lstlisting}

统计\verb|/home|目录下目录的个数
\begin{lstlisting}
ls -l /home | grep "^d" | wc -l
\end{lstlisting}

定位符\verb|^|表示开始的位置，\verb|"^d"|表示开头是d的

统计\verb|/home|目录下文件的个数，包括子文件夹里的。区别是\verb|ls|命令多了一个选项\verb|-R|表示递归到子目录。
\begin{lstlisting}
ls -lR /home | grep "^-" | wc -l
\end{lstlisting}

\paragraph{查看某个目录下每个文件夹的所占磁盘空间}
查看根目录下的文件夹
\begin{lstlisting}
sudo du -sh /*
\end{lstlisting}
查看\verb|/usr|下的文件夹
\begin{lstlisting}
sudo du -sh /usr/*
\end{lstlisting}
查看\verb|/var|下的文件夹
\begin{lstlisting}
sudo du -sh /var/*
\end{lstlisting}

\subsection{挂载 - mount命令}

\verb|mount|命令

将根目录以外的文件夹挂在到根目录下的某个目录(称为挂载点)，必须是已存在的某个目录

这个过程称为挂载，其逆向过程称为卸载\verb|unmount|

语法：

\begin{lstlisting}
mount –a [option]
mount [option] <源> <目录>
\end{lstlisting}

选项：

\begin{table}[!ht]
\centering
\caption{mount命令选项} \label{mount命令选项}
\begin{tabular}{|l|p{25em}|}
\hline
选项 & 功能  \\ \hline
\verb|-a| & 挂载fstab中的所有文件系统  \\  \hline
\verb|-o param| & 挂载的选项，后面跟着一个列表(可以有一个或多个选项)，主要包括loop, ro，rw，iocharset，remount \\ \hline
\verb|-t param| & 指定档案的系统类型，一般包括iso9660, nfs, msdos等 \\ \hline
\end{tabular}
\end{table}

开机自动挂载，可以修改文件\verb|/etc/fstab|，每行定义一个需要挂载的文件系统

\verb|/etc/fstab|，记录了计算机上硬盘分区的相关信息

\verb|/etc/mtab|，记录了现在系统已挂载的文件系统，每当运行\verb|mount|或者\verb|umount|命令，都会更新\verb|/etc/mtab|

\chapter{环境变量}
\verb|env|命令查看当前用户的全部环境变量

\verb|echo $变量名|查询某个单独的环境变量

\verb|export 变量名=值|配置某个环境变量

使用\verb|export|配置的环境变量，退出终端后即消失

\chapter{linux下的网络开发}

net-tools是linux网络开发的基础工具，主要包括\verb|arp|, \verb|ifconfig|, \verb|netstat|, \verb|route|等命令

\verb|netstat|命令，用于显示与IP, TCP, UDP和ICMP协议相关的统计数据，一般用于检验本机各端口的网络连接情况。

\subsection{route}
\verb|route|命令，显示和操作IP路由表。不加上任何选项，会解析主机名，可能会花费一些时间。

\subsubsection{route –n}
\verb|route –n|，不打印主机名，直接打印ip地址

以下面的输出为例，解读一下这个命令的使用
\begin{lstlisting}
$ route -n
内核 IP 路由表
目标            网关            子网掩码        标志  跃点   引用  使用 接口
0.0.0.0         192.168.101.254 0.0.0.0         UG    100    0        0 eno1
169.254.0.0     0.0.0.0         255.255.0.0     U     1000   0        0 eno1
172.17.0.0      0.0.0.0         255.255.0.0     U     0      0        0 docker0
192.168.101.0   0.0.0.0         255.255.255.0   U     100    0        0 eno1
\end{lstlisting}

\begin{enumerate}
    \item 目标:目标网络或主机的 IP 地址。0.0.0.0 表示默认路由。
    \item 网关：数据包发送到目标网络时使用的网关 IP 地址。0.0.0.0 表示无需网关。
    \item 标志：用于表示路由的标志。U表示该路由是活动的。G表示是使用网关的。
    \item 跃点：路由的优先级，数值越小优先级越高。
    \item 引用：路由的引用计数，通常为 0。
    \item 使用：该路由被使用的次数。
    \item 接口：数据包将通过哪个网络接口发送。
\end{enumerate}

所有发往 172.17.0.0/16 子网的数据包都将直接发送到 docker0 接口，而不需要经过其他网关。这是因为这些地址是在本地 Docker 网络内，可以直接访问。

\section{远程登录}
这段与个人PC上的linux系统使用关系不大，与公司集体使用的linux系统有关。linux安装在机房的服务器中，员工通过个人pc远程登录到机房的linux系统中使用linux系统进行操作。

Xshell5(远程登录到linux的软件)

XFtp5(远程上传与下载)

XShell是一个强大的安全终端模拟软件，它支持SSH1，SSH2以及Microsoft Windows平台的TELNET协议。

XShell可以在Windows界面下用来访问远端不同系统下的服务器，从而不较好的打倒远程控制终端的目的。

需要linux开启一个\verb|sshd|服务，该服务会监听22号端口(一个系统中理论上有65535个端口，端口开的越多，安全性就越低)，以便Xshell进行连接。

\part{开发工具}
\chapter{vi/vim}
\minitoc
\section{三种模式}
%\subsection{三种模式}
\begin{itemize}
    \item 正常模式、
    \item 插入模式(按ioar键都可以进入插入模式)
    \item 命令行模式(输入\verb|:|或者斜杠\verb|/|进入命令行模式)
\end{itemize}

其实，斜杠\verb|/|进入的主要是查找功能，可以看做是一种特殊的命令行模式。冒号\verb|:|也可以使用查找功能。

\verb|:q!|强制退出不保存

\verb|:wq|保存并推出

如果打开过后没有做任何修改，用\verb|:q|也可以直接退出

\section{操作}
\subsection{快捷键}
\begin{itemize}
    \item 正常模式下，按G跳转到最后一行，按gg跳转到第一行
    \item 撤销：正常模式下u
    \item 跳转到指定行：先显示行号，正常模式下，输入行号数字，再按shift+g
\end{itemize}

\subsection{拷贝，剪切与粘贴}
\subsubsection{拷贝与剪切}
拷贝的基本命令是\verb|y|，剪切的基本命令是\verb|d|

vim是没有删除的，但是可以利用剪切板来进行删除。

\verb|yy|，拷贝当前行

\verb|5yy|，拷贝当前行向下的5行

\verb|dd|，删除当前行

\verb|5dd|，删除当前行向下的5行

\subsubsection{粘贴}
粘贴的基本命令是\verb|p|

\verb|p|，粘贴到当前行的下一行

\verb|P|，粘贴到当前行的前一行

\subsubsection{寄存器}
寄存器，也就是剪贴板。vim是支持多个寄存器的，在拷贝和剪切的时候，是可以选择把内容暂存到哪个寄存器当中。

例如\verb|y0|将复制当前行或选定文本到0号寄存器中。

\subsection{打开/关闭文件的行号}
命令行模式下，\verb|set nu|和\verb|set nonu|

\subsection{查找与替换}
\subsubsection{查找}
在普通模式下，直接输入\verb|/[string]|就能查找字符串\verb|[string]|了。

下一个，\verb|N|，上一个，\verb|n|。跳转到最后一个再继续跳转，会回滚到第一个匹配项。

正向查找，\verb|:/|，反向查找，\verb|:?|。

查看之前的查找历史，\verb|:history|。

\subsubsection{替换}
在命令模式下，以\verb|:%s/pattern/replacement/flags|格式输入字符串，就是要执行替换操作。

其中\verb|pattern|是要查找的内容，可以给出正则表达式。\verb|replacement|是要替换的新内容。\verb|flags|是一些功能选项。
\begin{table}[ht]
    \centering
    \caption{功能选项}
    \begin{tabular}{|l|l|}
        \hline
        \verb|flags|选项 & 功能 \\ \hline
        \verb|g| & 全局替换 \\ \hline
        \verb|i| & 忽略大小写 \\ \hline
        \verb|h| & 将匹配项高亮 \\ \hline
        \verb|c| & 每次执行替换之前会询问用户是否进行这次替换，下方会给出y,n等各种选项 \\ \hline
    \end{tabular}
\end{table}

\section{可视块模式}
从正常模式(按esc进入)下，按ctrl+\verb|v|，可以进入可视块模式(Visual Block Mode)
\subsection{选择选区}
使用方向键\verb|hjkl|，和\verb|wbe|等命令，来调整选区

使用shift+\verb|hjkl|也可以调整选区

使用\verb|o|可以快速移动到选区的另一端

\paragraph{移动选区}
建立好选区之后，使用\verb|J|向下移动选区，\verb|K|向上移动选区。

\paragraph{使用命令I和A进行批量插入}
在可视块模式下，建立好选区之后，输入\verb|I|(大写I)，开始输入，再按esc退回到正常模式，则可以在每一行的行首插入相同的内容。可以用于批量增加行注释。

输入\verb|A|可以在行尾插入文本。

\paragraph{执行命令}
建立好选区之后，使用冒号\verb|:|可以对每一行执行相同的命令。比如，仅在选区内进行替换。

\chapter{binutils - linux开发工具}
\minitoc
\section{ld命令}
\paragraph{简介}
\verb|ld|命令，是二进制工具集GNU binutils的一员，是GNU链接器

\paragraph{命令格式}
\begin{lstlisting}
ld [options] objects
\end{lstlisting}

\paragraph{库文件的搜索路径}
环境变量\verb|LD_LIBRARY_PATH|是默认情况下用于搜索库文件的路径，它里面可以存储多个目录，以供动态链接器(\verb|ld-linux.so| 或者 \verb|ld-linux-x86-64.so.2| 等)来搜索库文件。

\paragraph{常用选项}
\begin{table}[!ht]
    \centering
    \caption{ld命令选项} \label{ld命令选项}
    \begin{tabular}{|l|p{25em}|}
        \hline
        选项 & 功能  \\ \hline
        \verb|-o file| & 链接结果输出到文件\verb|file|中 \\ \hline
        \verb|-l library| & 搜索名为\verb|liblibrary.so|或\verb|liblibrary.a|的库文件，并链接到输出文件 \\ \hline
        \verb|-L directory| & 添加directory到库文件的搜索路径 \\ \hline
        \verb|-static| & 使用静态链接 \\ \hline
        \verb|-shared| & 使用动态链接 \\ \hline
        \verb|-v, --verbose| & 显示详细的链接过程信息 \\ \hline
    \end{tabular}
\end{table}

\section{objdump - 查看二进制文件}
\paragraph{简介}
\verb|objdump|命令，是二进制工具集GNU binutils的一员，可以查看目标文件或可执行文件的组成信息，以可读的方式打印二进制文件的内容。目标文件和可执行文件都是二进制文件。

\paragraph{命令格式}
\begin{lstlisting}
objdump [options] objects
\end{lstlisting}

\paragraph{常用选项}
\begin{table}[!ht]
    \centering
    \caption{objdump命令选项} \label{objdump命令选项}
    \begin{tabular}{|l|p{25em}|}
        \hline
        选项 & 功能  \\ \hline
        \verb|-a|或\verb|-x| & 查看所有信息 \\ \hline
        \verb|-C| & 将底层的符号名解码成用户级名字，除了去掉所开头的下划线之外，还使得C++函数名以可理解的方式显示出来。  \\ \hline
        \verb|-d, --disassemble [filename]| & 将二进制文件反汇编成汇编语言文件  \\  \hline
        \verb|-f| & 显示文件的格式。显示objfile中每个文件的整体头部摘要信息。  \\ \hline
        \verb|-h| & 显示各个段的头部摘要信息，同readelf -S \\ \hline
        \verb|-H| & 查看帮助信息 \\ \hline
        \verb|-j [name]|或\verb|--section=[name]| & 仅仅显示指定名称为\verb|name|的section的信息  \\ \hline
        \verb|-r| & 显示重定位信息 \\ \hline
        \verb|-S| & 保留源代码行号与汇编指令对应的信息 \\ \hline
        \verb|–s| & 代码段、数据段、只读数据段，各个段二进制 \\ \hline
        \verb|-t| & 显示符号表 \\ \hline
        \verb|-T| & 显示动态符号表 \\ \hline
    \end{tabular}
\end{table}

\paragraph{查看特定函数的反汇编}
\begin{lstlisting}
objdump -d <filename> --start-address=<address> --stop-address=<address>
\end{lstlisting}

\paragraph{反汇编给定程序中的text段的内容}
\begin{lstlisting}
objdump -j .text -S pass_parm

pass_parm：     文件格式 elf64-x86-64


Disassembly of section .text:

0000000000400430 <_start>:
400430:	31 ed                	xor    %ebp,%ebp
400432:	49 89 d1             	mov    %rdx,%r9
400435:	5e                   	pop    %rsi
400436:	48 89 e2             	mov    %rsp,%rdx
400439:	48 83 e4 f0          	and    $0xfffffffffffffff0,%rsp
40043d:	50                   	push   %rax
...略
\end{lstlisting}

\paragraph{显示目标文件各个段的头部摘要信息}
\begin{lstlisting}
objdump -h pass_parm

pass_parm：     文件格式 elf64-x86-64

节：
Idx Name          Size      VMA               LMA               File off  Algn
0 .interp       0000001c  0000000000400238  0000000000400238  00000238  2**0
CONTENTS, ALLOC, LOAD, READONLY, DATA
1 .note.ABI-tag 00000020  0000000000400254  0000000000400254  00000254  2**2
CONTENTS, ALLOC, LOAD, READONLY, DATA
2 .note.gnu.build-id 00000024  0000000000400274  0000000000400274  00000274  2**2
CONTENTS, ALLOC, LOAD, READONLY, DATA
3 .gnu.hash     0000001c  0000000000400298  0000000000400298  00000298  2**3
CONTENTS, ALLOC, LOAD, READONLY, DATA
4 .dynsym       00000060  00000000004002b8  00000000004002b8  000002b8  2**3
CONTENTS, ALLOC, LOAD, READONLY, DATA
5 .dynstr       0000003f  0000000000400318  0000000000400318  00000318  2**0
CONTENTS, ALLOC, LOAD, READONLY, DATA
6 .gnu.version  00000008  0000000000400358  0000000000400358  00000358  2**1
CONTENTS, ALLOC, LOAD, READONLY, DATA
7 .gnu.version_r 00000020  0000000000400360  0000000000400360  00000360  2**3
CONTENTS, ALLOC, LOAD, READONLY, DATA
8 .rela.dyn     00000018  0000000000400380  0000000000400380  00000380  2**3
CONTENTS, ALLOC, LOAD, READONLY, DATA
...略
\end{lstlisting}

\section{od - 查看二进制文件内容}
\verb|od|命令的基本格式是
\begin{lstlisting}
    od [选项] [文件]
\end{lstlisting}

\paragraph{常用的选项}
\begin{table}[ht]
    \centering
    \caption{od命令的常用选项} \label{od命令的常用选项}
    \begin{tabular}{|l|l|}
        \hline
        选项 & 功能 \\ \hline
        \verb|-t type| & 指定输出的格式 \\ \hline
        \verb|-t oN| & 八进制输出，其中\verb|N|是一个数字，表示每个数据项的字节数。 \\ \hline
        \verb|-t aN| & ascii字符 \\ \hline
        \verb|-t dN| & 有符号十进制数 \\ \hline
        \verb|-t uN| & 无符号十进制数 \\ \hline
        \verb|-t xN| & 十六进制数 \\ \hline
        \verb|-A n/o/x/d| & 指定地址基数，\verb|n|为不显示地址，oxd分别表示八进制，十六机制和十进制 \\ \hline
        \verb|-N| & 指定要读取的字节数 \\ \hline
        \verb|-j| & 跳过指定数量的字节 \\ \hline
        \verb|-v| & 显示所有字节，包括空字节。 \\ \hline
        \verb|-w| & 指定每行输出的字节数 \\ \hline
    \end{tabular}
\end{table}

显示文件的前 20 个字节的十六进制表示
\begin{lstlisting}
od -N 20 -x filename
\end{lstlisting}

跳过前 10 个字节，然后显示接下来的 20 个字节的十六进制表示
\begin{lstlisting}
od -j 10 -N 20 -x filename
\end{lstlisting}

显示文件的每行 16 个字节的十六进制表示
\begin{lstlisting}
od -w 16 -x filename
\end{lstlisting}

从标准输入读取并显示十六进制表示
\begin{lstlisting}
echo "Hello, World!" | od -x
\end{lstlisting}

\section{readelf命令}
\paragraph{简介}
\verb|readelf|命令，查看可执行已链接二进制文件的内容，不是无序的二进制构造。

\verb|readelf|命令与\verb|objdump|命令有点类似

BFD库(binary file descriptor library)，希望使用一个统一的接口来处理不同的目标文件格式。BFD是binutils项目的一个子项目。\verb|objdump|命令是BFD库的一部分，而\verb|readelf|命令与BFD库无关。所以即使 BFD 库有什么bug存在的话也不会影响到 \verb|readelf| 命令。

\begin{table}[!ht]
    \centering
    \caption{readelf命令选项} \label{readelf命令选项}
    \begin{tabular}{|l|p{25em}|}
        \hline
        选项 & 功能  \\ \hline
        \verb|-a| & 显示所有信息  \\  \hline
        \verb|-h [filename]| & 查看文件的ELF头(ELF Header)  \\  \hline
        \verb|-l [filename]| & 查看程序头 \\ \hline
        \verb|-S [filename]| & 查看节头 \\ \hline
    \end{tabular}
\end{table}

\paragraph{ELF文件}
使用g++仅编译成目标文件*.o，是不能直接运行的。通过readelf命令查看，发现区别，*.o文件没有程序头。从ELF头(ELF Header)可以看出，*.o文件的类型是REL可重定位文件(relocatable file)，而*.out的类型是DYN共享目标文件。

ELF文件主要包括三种，REL，DYN，EXEC可执行文件

\section{file命令}
\verb|file|命令，查看文件的类型

\paragraph{命令格式}
\begin{lstlisting}
file [-bcLvz][-f <名称文件>][-m <魔法数字文件>...][文件或目录...]
\end{lstlisting}

\paragraph{常用选项}
\begin{table}[!ht]
    \centering
    \caption{file命令选项} \label{file命令选项}
    \begin{tabular}{|l|p{25em}|}
        \hline
        选项 & 功能  \\ \hline
        \verb|-b| & 列出辨识结果时，不显示文件名称。 \\ \hline
        \verb|-c| & 详细显示指令执行过程，便于排错或分析程序执行的情形。 \\ \hline
        \verb|-L [filename]| & 查看软链接所指向的文件的类型，而不是软链接本身 \\ \hline
    \end{tabular}
\end{table}

\section{ldd命令}
\verb|ldd|命令，是list dynamic dependencies的缩写，列出文件的动态库的依赖关系

\verb|ldd|不是一个可执行程序，而只是一个shell脚本

\paragraph{底层原理}
其原理是通过设置一系列的环境变量，比如 \verb|LD_TRACE_LOADED_OBJECTS|、\verb|LD_WARN|、\verb|LD_BIND_NOW|、\verb|LD_LIBRARY_VERSION|、\verb|LD_VERBOSE|等。当\verb|LD_TRACE_LOADED_OBJECTS|环境变量不为空时，任何可执行程序在运行时，它都会只显示模块的dependency(所属)，而程序并不真正执行。

可以在shell终端测试一下，比如运行 \verb|export LD_TRACE_LOADED_OBJECTS=1| 再执行任何的程序，如\verb|ls|等，看看程序的运行结果。

\verb|ldd|显示可执行模块的dependency(所属)的工作原理，其实质是通过\verb|ld-linux.so|(elf动态库的装载器)来实现的。我们知道，\verb|ld-linux.so|模块会先于executable模块程序工作，并获得控制权，因此当上述的那些环境变量被设置时，\verb|ld-linux.so|选择了显示可执行模块的dependency(所属)。 

实际上可以直接执行\verb|ld-linux.so|模块，如：\verb|/lib/ld-linux.so.2 --list program|(这相当于\verb|ldd program|)

\section{stat命令}
\verb|stat|命令，可以查看文件的详细信息，比\verb|ls|显示的信息要丰富得多。

\verb|stat|命令，可以查看最后一次访问时间(即，access)，最后一次修改内容的时间(modify)，最后一次修改属性的时间(change)

\verb|-f|，查看所在文件系统的状态信息

\chapter{lockdep – linux内核死锁检测工具}

在Linux内核中，常见的死锁有如下两种：

\begin{enumerate}
    \item 归死锁：如在中断延迟操作中使用了锁，和外面的锁构成了递归死锁。
    \item AB-BA死锁：多个锁因处理不当而引发死锁，多个内核路径上的锁处理顺序不一致也会导致死锁。
\end{enumerate}

Linux内核在2006年引入了死锁调试模块lockdep，lockdep会跟踪每个锁的自身状态和各个锁之间的依赖关系，经过一系列的验证规则来确保锁之间依赖关系是正确。

要在Linux内核中使用lockdep功能，需要打开\verb|CONFIG_DEBUG_LOCKDEP|选项

\begin{lstlisting}
CONFIG_LOCK_STAT=y
CONFIG_PROVE_LOCKING=y
CONFIG_DEBUG_LOCKDEP=y
\end{lstlisting}

在proc目录下会有\verb|lockdep|、\verb|lockdep_chains|和\verb|lockdep_stats|三个文件节点，这说明\verb|lockdep|模块已经生效

然后重新编译内核，更换内核重启系统。

\chapter{ThreadSanitizer – 动态数据竞争检测工具}

知乎文章：\href{https://zhuanlan.zhihu.com/p/38687826}{https://zhuanlan.zhihu.com/p/38687826}

ThreadSanitizer是检测数据争用的工具。它由一个编译器检测模块和一个运行时库组成。Thread Sanitizer会记录每一个内存访问的信息，并检测该访问是否参与了竞争。

ThreadSanitizer(简称tsan)是一款可以帮你检测大多数的data race的工具。不但如此，它还专门发了篇论文[1]介绍了原理(ps 相关理论和工具其实很早就有了)。本质上，这是一次对于Lamport Clock在多线程领域的应用，因此阅读之前推荐看下Lamport老爷子的[3]

race condition和data race这两个术语，是不同的概念。在这篇文章中有更详细的解释(链接：https://blog.regehr.org/archives/490)

tsan本质是一个状态机。在程序执行期间，它会一直观测各类事件(event)，并维护了一个全局的事件队列。每当一个新的事件发生时，tsan会将其加入到事件队列中，并更新自己的状态，然后通过状态判断这个事件是否导致了data race。

tsan中的事件可以分为两大类：内存访问(memory access)事件和同步(synchronization)事件。

内存操作事件比较简单，就是读(Read)和写(Write)两种。

而同步事件可以进一步细分成锁(locking)事件和happens-before(囧rz，实在不会翻译)事件。锁事件包括了WriterLock，WriterUnlock，ReaderLock以及ReaderUnlock四种。happens-before事件包括了Signal和Wait两种。本质上，这对事件对应一个通道(channel)上的Send和Receive操作([3])。

为了简单起见，本文的讨论将不细分ReaderLock和WriterLock。因此锁事件可以被简化成两种，Lock和Unlock。下图是tsan中所有事件类型的一个总结。

\begin{figure}[h]\centering
\caption{ThreadSanitizer} \label{ThreadSanitizer}
\includegraphics{ThreadSanitizer.png}
\end{figure}

参考文献：

[1] Serebryany, K. and Iskhodzhanov, T., 2009, December. ThreadSanitizer: data race detection in practice. InProceedings of the Workshop on Binary Instrumentation and Applications(pp. 62-71). ACM.

[2] O'Callahan, R. and Choi, J.D., 2003, June. Hybrid dynamic data race detection. InAcm Sigplan Notices(Vol. 38, No. 10, pp. 167-178). ACM.

[3] Lamport, L., 1978. Time, clocks, and the ordering of events in a distributed system.Communications of the ACM,21(7), pp.558-565.

[4] Mattern, F., 1989. Virtual time and global states of distributed systems.Parallel and Distributed Algorithms,1(23), pp.215-226.

线程检查工具threadsanitizer，类似地还有addresssanitizer，memorysanitizer，ubsanitizer(undefined behaviour)

\chapter{AddressSanitizer – 内存检测工具}

知乎文章：\href{https://zhuanlan.zhihu.com/p/37515148}{https://zhuanlan.zhihu.com/p/37515148}

Google出品的内存检测工具AddressSanitizer

它与传统的内存问题检测工具例如 Valgrind之间的区别，就是AddressSanitizer减缓程序运行速度的程度更低。

在LLVM及高版本编译器中已经自带了该工具。ASan基于shadow memory实现，目前已经集成到Clang 3.1和GCC 4.8以上版本。

编译时添加\verb|-fsanitize=address|选项。正常运行程序，如有内存相关问题，即会打印异常信息。

\verb|-fsanitize=leak|，只会使能Leak Sanitizer，检测内存泄漏问题

\verb|-fno-omit-frame-pointer|，检测到内存错误时打印函数调用栈
\verb|-O1|，代码优化选项，可以打印更清晰的函数调用栈

这个项目包含了AddressSanitizer，MemorySanitizer，ThreadSanitizer和LeakSanitizer等工具。这些工具可以检测用户空间的内存问题。通过在编译时加入指定的选项，就可以给用户程序加入Address Sanitizer功能。

其中Address Sanitizer(ASAN)工具是一个内存错误检测器，可以检测以下问题：

\begin{enumerate}
\item Out-of-bounds accesses to heap, stack and globals 堆、栈以及全局变量越界
\item Use-after-free 即访问dangling pointer，已经free的指针
\item Use-after-return (to some extent)
\item Double-free, invalid free
\item Memory leaks (experimental)
\end{enumerate}

\chapter{git - 分布式版本管理工具}
\minitoc
\section{git基本概念}
\subsection{简介 - 什么是git}

git是一个开源的分布式版本控制系统，用于敏捷高效地处理任何或小或大的项目。

Git 是 Linus Torvalds 为了帮助管理 Linux 内核开发而开发的一个开放源码的版本控制软件。

Git 与常用的版本控制工具 CVS, Subversion 等不同，它采用了分布式版本库的方式，不必服务器端软件支持。

\paragraph{git和svn的区别}

\begin{enumerate}
	\item Git 是分布式的，SVN 不是
	\item Git 把内容按元数据方式存储，而 SVN 是按文件
	\item Git 分支和 SVN 的分支不同
	\item Git 没有一个全局的版本号，而 SVN 有
	\item Git 的内容完整性要优于 SVN：Git 的内容存储使用的是 SHA-1 哈希算法。这能确保代码内容的完整性，确保在遇到磁盘故障和网络问题时降低对版本库的破坏
\end{enumerate}

Git 目前支持 Linux/Unix、Solaris、Mac和 Windows 平台上运行

\paragraph{查看git版本}

\verb|git --version|

\paragraph{git的工作流程}

git的工作流程(见图\ref{git的工作流程}，第\pageref{git的工作流程}页)

来源：菜鸟教程

\begin{figure}[h]\centering
\caption{git的工作流程} \label{git的工作流程}
\includegraphics[width=0.7\textwidth]{git-process.png}
\end{figure}

\subsection{配置git}

使用命令\verb|git config|，来配置或读取相应的工作环境变量

\paragraph{环境变量}

这些环境变量，决定了 Git 在各个环节的具体工作方式和行为。这些变量可以存放在以下三个不同的地方

\begin{enumerate}
	\item \verb|/etc/gitconfig|文件: 系统中对所有用户都普遍适用的配置。若使用 \verb|git config| 时用 \verb|--system| 选项，读写的就是这个文件
	\item \verb|~/.gitconfig|文件: 用户目录下的配置文件只适用于该用户。若使用 \verb|git config| 时用 \verb|--global| 选项，读写的就是这个文件
	\item 当前项目的Git目录中的配置文件(也就是工作目录中的\verb|.git/config|文件): 这里的配置仅仅针对当前项目有效。每一个级别的配置都会覆盖上层的相同配置，所以 \verb|.git/config| 里的配置会覆盖 \verb|/etc/gitconfig| 中的同名变量
\end{enumerate}

\verb|git config -e|，选项\verb|-e|表示打开编辑器

\verb|git config|针对当前仓库 

\verb|git config --global|针对系统上所有仓库

\paragraph{用户信息}

配置个人的用户名称和电子邮件地址：

\begin{lstlisting}
git config --global user.name "runoob"
git config --global user.email test@runoob.com
\end{lstlisting}

如果用了 \verb|--global| 选项，那么更改的配置文件就是位于你用户主目录下的那个，以后你所有的项目都会默认使用这里配置的用户信息。

如果要在某个特定的项目中使用其他名字或者电子邮箱，只要去掉 \verb|--global| 选项重新配置即可，新的设定保存在当前项目的 \verb|.git/config| 文件里

\paragraph{文本编辑器}

设置Git默认使用的文本编辑器, 一般可能会是 Vi 或者 Vim。如果你有其他偏好，比如 Emacs 的话，可以重新设置

\verb|git config --global core.editor emacs|

\paragraph{差异分析工具}

\verb|git config --global merge.tool vimdiff|

Git 可以理解 kdiff3，tkdiff，meld，xxdiff，emerge，vimdiff，gvimdiff，ecmerge，和 opendiff 等合并工具的输出信息。

甚至可以指定使用自己开发的工具

\paragraph{查看配置信息}

要检查已有的配置信息，可以使用 \verb|git config --list| 命令

\subsection{工作区，暂存库，版本库}

\subsubsection{简介}

\begin{enumerate}
	\item 工作区：就是你在电脑里能看到的目录。
	\item 暂存区：英文叫 \verb|stage| 或 \verb|index|。一般存放在 \verb|.git| 目录下的 \verb|index| 文件(\verb|.git/index|)中，所以我们把暂存区有时也叫作索引(\verb|index|)。
	\item 版本库：工作区有一个隐藏目录 \verb|.git|，这个不算工作区，而是 git 的版本。存放了所有的历史更改记录，是git文件的最主要部分。
\end{enumerate}

\begin{figure}[htbp]\centering
	\caption{工作区、暂存区和版本库之间的关系} \label{stage and index}
	\includegraphics[width=1\textwidth]{stage and index.jpg}
\end{figure}

\subsubsection{HEAD}
\verb|HEAD|指向当前所处的分支(branch)

一般情况下，\verb|HEAD|指向当前分支的最新的commit。这意味着，\verb|HEAD|所指向的结点，将是下一次提交的父节点，下一次的提交，将接续着当前\verb|HEAD|所指向的commit。

当前这是指一般情况，所以一定会有特殊情况，即，\verb|HEAD|并不指向某一个分支的最新的commit，而是指向中途的某次commit，此时发生的现象称为"头指针分离"。

\subsection{创建仓库}

\subsubsection{创建本地仓库}

使用 \verb|git init| 命令来初始化一个 Git 仓库。Git 的很多命令都需要在 Git 的仓库中运行，所以 \verb|git init| 是使用 Git 的第一个命令

该命令执行完后会在当前目录生成一个\verb|.git|目录

在本地使用git时：

后面所提到的使用git进行版本控制的指令，基本都是在\verb|.git|目录所在的目录，也就是git本地仓库所在的目录下运行的。

如果当前所处的目录下没有git仓库，后文提到的这些指令不会起效。

\subsubsection{克隆仓库}

我们使用 \verb|git clone| 从现有 Git 仓库中拷贝项目

如果我们需要克隆到指定的目录，可以使用命令\verb|git clone <repo> <directory>|

\section{常用命令}

Git 常用的是以下 6 个命令：\verb|git clone|克隆、\verb|git push|推送、\verb|git add|添加 、\verb|git commit|提交、\verb|git checkout|、\verb|git pull|

\begin{figure}[htbp]\centering
	\caption{git常用的6个命令} \label{git-command}
	\includegraphics[width=1\textwidth]{git-command.jpg}
\end{figure}

\begin{enumerate}
	\item workspace：工作区
	\item staging area：暂存区/缓存区
	\item local repository：版本库或本地仓库
	\item remote repository：远程仓库
\end{enumerate}

\subsection{添加(add)与提交(commit)}

\subsubsection{git add}

\verb|git add| 命令可将该文件添加到暂存区

\verb|git add [file1] [file2] ...|：添加单个或多个文件

\verb|git add [dir]|：添加某个目录

\verb|git add .|：添加当前整个目录

\subsubsection{git commit}

\verb|git commit| 命令将暂存区内容添加到本地仓库中

提交暂存区到本地仓库中，这里的message代表一些备注信息：\verb|git commit -m [message]|

如果不加上\verb|-m [message]|部分，则会打开默认的文本编辑器来写备注信息

不写备注是不允许提交的，git要求每一条commit都要有备注

\verb|-a| 参数设置修改文件后不需要执行 \verb|git add| 命令，直接来提交：\verb|git commit -a|

\paragraph{--amend选项}
\verb|--amend|选项，可以修改上一次提交的注释信息以及文件，是可以快速修改最近1次commit的一种方法，比\verb|git reset|或者\verb|revert|更轻量级的指令。

直接使用\verb|git commit --amend|会弹出一个文本编辑器让你修改上一次提交的注释信息。

如果把其他修改通过\verb|git add|添加到暂存区，然后使用\verb|git commit --amend|，就可以让暂存区的改动，也同步到上一次commit当中。

\subsection{查看状态(status)对比(diff)}

\subsubsection{git status}

格式：\verb|git status [options]|

当执行 \verb|git status| 的时候，返回结果大致可分为 3 个部分

\begin{table}[ht]
\centering
\caption{git status返回的结果} \label{git status返回的结果}
\begin{tabular}{|l|l|}
    \hline
    返回结果   &                                                                     \\ \hline
    拟提交的变更 & 这是已经放入暂存区，准备使用 \verb|git commit| 命令提交的变更                            \\ \hline
    未暂存的变更 & 这是工作目录和暂存区快照之间存在差异的文件列表。                                            \\ \hline
    未跟踪的文件 & 这类文件对于 Git 系统来说是未知的，如果不使用\verb|git add|或者\verb|git commit|，则很容易就丢失。 \\ \hline
\end{tabular}
\end{table}

使用 \verb|-s| 参数来获得简短的输出结果

此时返回的格式如下：

\verb|XY PATH1|

X 和 Y 都是状态码，X 表示暂存区和最近一次提交的差异，Y 表示工作目录和暂存区的差异。具体状态码解释如下

\begin{table}[ht]
    \centering
    \caption{git status -s显示的状态码} \label{git status -s显示的状态码}
    \begin{tabular}{|l|l|}
        \hline
        状态码       & 描述                   \\ \hline
        \verb|..| & unmodified           \\ \hline
        \verb|M|  & modified             \\ \hline
        \verb|A|  & added                \\ \hline
        \verb|D|  & deleted              \\ \hline
        \verb|R|  & renamed              \\ \hline
        \verb|C|  & copied               \\ \hline
        \verb|U|  & updated but unmerged \\ \hline
        \verb|??| & untracked            \\ \hline
        \verb|!!| & ignored              \\ \hline
    \end{tabular}
\end{table}

\subsubsection{git diff}

\verb|git diff|：查看暂存区(staging area, index)和工作区(workspace)之间的差异

\verb|git diff commitId|查看工作区(workspace)与某次commit之间的差异

\verb|git diff --cached|：查看暂存区与最近一次提交之间的改动差异

\verb|git diff HEAD|：显示工作目录(已track但未add文件)和暂存区(已add但未commit文件)与最后一次commit之间的的所有不相同文件的增删改。

\verb|git diff --stat|：显示摘要而非整个 diff

\verb|git diff commitId1 commitId2|：显示两次提交之间的差异

在上述每一个\verb|git diff|命令语句，在其后面都可以加上一个路径或者文件名，可以比较某路径下或者单独某个文件的差异

\subsection{检出(checkout)}\label{检出(checkout)}
\verb|git checkout|，将某次commit的代码，提取到工作区。

\verb|git checkout branchName|切换分支到\verb|branchname|

\verb|git checkout .|放弃工作区中全部的修改

\verb|git checkout -- filename|放弃工作区中某个文件的修改

\paragraph{头指针分离}
\verb|git checkout commitId|检出到某一个commit，HEAD会指向给定的commit，并且进入"头指针分离"的状态。

如果想恢复到原来的状态，只需要检出到之前所在的branch即可，即\verb|git checkout branchName|，HEAD指针则会指向给定branch的最新的commit。

此时，使用\verb|git branch|能够查看目前本地有哪些分支，也能看到自己当前正处于头指针分离状态。
\begin{lstlisting}
User-OptiPlex-7080:~/code$ git branch
* (HEAD detached at 6d3040a0)
dev/User/DatabaseInterface
dev/User/DebugJsonInterface
dev/User/GamepadDriverUpgrade
master
\end{lstlisting}

\paragraph{将某一commit的代码提取到工作区，而不改变HEAD和分支}
\verb|git checkout <commit-id> .|

注意最后面的\verb|.|，一定要加上。加上，将工作区更新为指定commit的状态。如果不加上，则还会变更头指针HEAD，导致产生头指针分离的情况。

\subsection{版本回退(reset)}

\verb|git reset|命令

格式:

\begin{verbatim}
	git reset [--soft | --mixed | --hard] [HEAD]
\end{verbatim}

\begin{table}[ht]
    \centering
    \caption{git reset的三个选项} \label{git reset的三个选项}
    \begin{tabular}{|l|l|}
        \hline
        \verb|--mixed| & 是默认参数，重置暂存区的文件与上一次的提交(\verb|commit|)保持一致，工作区文件内容保持不变。 \\ \hline
        \verb|--soft|  & 用于回退到某个版本，工作区内容不会被修改。                                \\ \hline
        \verb|--hard|  & 撤销工作区中所有未提交的修改内容，将暂存区与工作区都回到上一次版本，并删除之前的所有信息提交。       \\ \hline
    \end{tabular}
\end{table}

\verb|git reset HEAD|：暂存区的目录树会被重写，被当前分支指向的目录树所替换，但是工作区不受影响。

\paragraph{上一次commit不理想，想重新提交}
\begin{enumerate}
    \item \verb|git reset --soft [commitId]| 回退到之前的某个版本，只修改HEAD，不修改工作区的代码，并不修改所有修改历史。
    \item \verb|git commit| 重新提交commit
\end{enumerate}

\subsection{版本重做(revert)}

\verb|git revert|，与\verb|git reset|的区别在于：

使用\verb|git reset --hard|之后，会直接回退到某次给定id的commit，HEAD指针会指向这次commit，而其之后的commit都会消失。

而使用\verb|git revert|之后，仅仅将给定id的commit取消掉，在其之后的commit并不会受影响，然后在git树结构上会新形成一个commit，HEAD指针会这个新的commit。

\subsection{查看日志(log, blame, show)}

\subsubsection{git log}

\verb|git log|：查看历史提交记录

\verb|--oneline|选项，可以来查看历史记录的简洁的版本，版本哈希码只显示前7个字母，commit备注内容会压缩到一行内显示。

\verb|--graph| 选项，查看历史中什么时候出现了分支、合并。以下为相同的命令，开启了拓扑图选项

\verb|--reverse|，逆向显示所有日志

\verb|git log --author=[authorName]|，显示某一作者提交的所有日志

指定日期，可以使用\verb|--since| 和 \verb|--before|，也可以用 \verb|--until| 和 \verb|--after|

\verb|git log --decorate|，查看\verb|git tag|给出的标签

\subsubsection{git blame}
以列表形式查看指定文件的历史修改记录：\verb|git blame <file>|

\verb|blame|输出的格式为：

\begin{lstlisting}
commit ID  (代码提交作者  提交时间  代码位于文件中的行数)  实际代码
\end{lstlisting}

\subsubsection{git show}
显示对象的详情，这里的\verb|<object>|可以是分支，可以是commitID，可以是某次提交中某个文件，也可以是标签，可以填入\verb|HEAD|，直接查看当前\verb|HEAD|指针指向的提交。
\begin{lstlisting}
git show [options] <object>
\end{lstlisting}

\subsection{合并(merge)}

格式：\verb|git merge branchName|

将分支\verb|branchName|合并到当前所在的分支。例如，有分支\verb|a|，想要将分支\verb|a|合并到主分支\verb|master|中，则首先要切换到主分支\verb|master|，然后再运行\verb|git merge a|。

\section{远程操作(remote, fetch, pull, push)}
\subsection{git clone}
\verb|git clone|是从远端的代码管理服务器，克隆一个仓库到本地。这样我们的电脑中就有一个本地仓库。此时的修改发生在本地仓库上，并未发生在远端仓库上。

\paragraph{基本用法}
\verb|git clone|后面紧跟着仓库的url
\begin{lstlisting}
git clone <版本库的url>
\end{lstlisting}

通往仓库的连接方式，可以是https的，也可以是ssh的，其示例如下。
\begin{lstlisting}
git clone https://github.com/tensorflow/tensorflow.git
git clone git@github.com:tensorflow/tensorflow.git
\end{lstlisting}

\paragraph{本地仓库-远端仓库之间的映射关系}
假如我们使用\verb|git clone https://github.com/user1/repository|克隆一个仓库，然后使用\verb|git remote -v|来查看一下本地-远端的映射关系，可能看到类似如下的信息。
\begin{lstlisting}
`origin` https://github.com/user1/repository.git (fetch)
`origin` https://github.com/user1/repository.git (push)
\end{lstlisting}

\verb|origin|是git为你创建的一个，表示远端仓库的一个标识符，默认命名是\verb|origin|，也可以命名为其他的。

增加本地-远端映射关系，可以使用\verb|git remote add|命令。
\begin{lstlisting}
git remote add upstream https://github.com/user1/repository.git

`upstream` https://github.com/user1/repository.git (fetch)
`upstream` https://github.com/user1/repository.git (push)
\end{lstlisting}

\subsection{git remote - 与远端有关的操作}
前面已经讲过了一些与\verb|git remote|有关的命令。

比如\verb|git remote -v|查看本地-远端映射关系。

\verb|git remote add [name] [url]|增加本地-远端映射关系。

\verb|git remote rename <old_name> <new_name>|，将已配置的远程仓库重命名。

\verb|git remote remove <remote_name>|，从当前仓库中删除指定的远程仓库。

\verb|git remote set-url <remote_name> <new_url>|，修改指定远程仓库的 URL。

\verb|git remote show <remote_name>|，显示指定远程仓库的详细信息，包括 URL 和跟踪分支。

\subsection{git pull - 拉取远端分支同步到本地仓库}
\verb|git pull|的功能是，拉取远端分支同步到本地仓库。而\verb|git clone|是直接克隆整个仓库。

\subsection{git fetch - 更新远端代码到本地仓库}
\verb|git fetch|是把远端的代码拉到本地，但是拉取的是远端的哪一个版本呢？

\paragraph{FETCH\_HEAD}
与\verb|git fetch|有关的一个概念是\verb|FETCH_HEAD|，指的是某个分支在服务器端上的最新状态。这个列表保存在 \verb|.Git/FETCH_HEAD| 文件中，其中每一行对应于远程服务器的一个分支。

\begin{itemize}
	\item 如果没有显式的指定远程分支, 则远程分支的 \verb|master| 将作为默认的 \verb|FETCH_HEAD|
	\item 如果指定了远程分支, 就将这个远程分支作为 \verb|FETCH_HEAD|
\end{itemize}

\paragraph{举例}

\begin{lstlisting}
# 方法一
$ git fetch origin master  #从远程的origin仓库的master分支下载代码到本地的origin master
$ git log -p master.. origin/master  #比较本地的仓库和远程参考的区别
$ git merge origin/master  #把远程下载下来的代码合并到本地仓库，远程的和本地的合并
\end{lstlisting}

\begin{lstlisting}
# 方法二
$ git fetch origin master:temp  #从远程的origin仓库的master分支下载到本地并新建一个分支temp
$ git diff temp  #比较master分支和temp分支的不同
$ git merge temp  #合并temp分支到master分支
$ git branch -d temp  #删除temp
\end{lstlisting}

\subsection{git push - 向远端推送提交}
向远端推送提交，使用\verb|git push|命令，第一个参数是远端仓库的标识符，第二个参数是本地的分支。
\begin{lstlisting}
git push origin master
git push upstream master
\end{lstlisting}

\section{分支(branch)}
几乎每一种版本控制系统都以某种形式支持分支，一个分支代表一条独立的开发线。使用分支意味着你可以从开发主线上分离开来，然后在不影响主线的同时继续工作。

Git 分支实际上是指向更改快照的指针。有人把 Git 的分支模型称为必杀技特性，而正是因为它，将 Git 从版本控制系统家族里区分出来。

\begin{figure}[htbp]\centering
	\caption{分支概念如何在分布式版本管理中起作用} \label{git-branch}
	\includegraphics[width=1\textwidth]{git-branch.png}
\end{figure}

\subsection{常用基础命令}
\verb|git branch [branchname]|：创建分支命令

\verb|git branch|：查看当前有哪些分支

\verb|git branch -d [branchname]|：删除分支命令

\verb|git branch -r|：查看远端分支

\verb|git branch -vv|，列出所有本地分支与远程分支，及其对应关系，和远端分支的最新commit简要信息。

\verb|git checkout [branchname]|：切换分支命令，实际上是切换到指定分支的最新的commit，即，将HEAD指针，指向这一分支的最新的commit。详细内容参见\ref{检出(checkout)}一节。

\verb|git merge|：合并分支命令。详细内容，参见\ref{分支合并}一节。

\paragraph{重命名分支}
先考虑仅涉及本地分支的情况。要想重命名一个本地分支，要先切换到其他分支，然后运行如下命令。
\begin{lstlisting}
git branch -m old-branch-name new-branch-name
\end{lstlisting}

\paragraph{删除本地对远程已经不存在的分支的引用}
使用\verb|git fetch|的\verb|-p|、\verb|--prune|，可以删除本地的对远端已经不存在的分支的引用
\begin{lstlisting}
git fetch --prune origin
git fetch -p origin
\end{lstlisting}

\subsection{分支合并}\label{分支合并}
在最常见的使用场景中，\verb|git merge|被用来合并两个分支。在这种场景中，\verb|git merge|接受两个commit指针，通常是两个分支的顶部commit，然后向前追溯到这两个分支最近的一个共同的父commit。

一旦找到这个共同提交，Git就会创建一个新的"merge commit"，用来合并两个分支上各自的提交序列。

merge commit与普通commit不一样，因为合并commit会有两个父提交。创建一个合并commit时Git会尝试自动将两个独立的提交历史合并为一个。

\paragraph{不自动生成commit}
如果不想自动创建合并的commit，则可以使用\verb|--no-commit|选项，这样他只会处理一下合并操作，但是并不会生成一个对应的commit，想要生成commit再手动提交commit。

\paragraph{合并冲突}
不过当Git发现某一块数据在两边的提交历史中都含有变更，它将无法自动对其进行合并。这种情况被称为\textbf{版本冲突}，此时Git需要人为介入调整才能继续进行合并。
\begin{figure}[htbp]\centering
    \caption{合并分支之前} \label{git_branch_before_merge}
    \includegraphics[width=0.7\textwidth]{git_branch_before_merge.png}
\end{figure}

\begin{figure}[htbp]\centering
    \caption{合并分支之后} \label{git_merge_after_merge}
    \includegraphics[width=0.7\textwidth]{git_merge_after_merge.png}
\end{figure}

\section{标签(tag)}

如果你的项目开发到达一个重要节点，并希望着重标记此时这个特别的提交快照，你可以使用 \verb|git tag| 给它打上标签。

比如说，我们想为我们的 runoob 项目发布一个"1.0"版本。 我们可以用 \verb|git tag -a v1.0| 命令给最新一次提交打上\verb|(HEAD)"v1.0"|的标签。

\verb|-a| 选项意为"创建一个带注解的标签"。 不用 \verb|-a| 选项也可以执行的，但它不会记录这标签是啥时候打的，谁打的，也不会让你添加个标签的注解

\verb|git tag|或者\verb|git tag -l|：查看本地所有tag

\verb|git tag <tagName>|：创建本地tag

\verb|git tag [标签名] [提交版本]|：给某个版本添加tag

\verb|git tag -d <tagName>|：删除tag

\paragraph{tag和branch的区别}

tag 对应某次commit, 是一个点，是不可移动的。

branch 对应一系列commit，是很多点连成的一根线，有一个HEAD 指针，是可以依靠 HEAD 指针移动的。

所以，两者的区别决定了使用方式，改动代码用 branch ,不改动只查看用 tag。

tag 和 branch 的相互配合使用，有时候起到非常方便的效果，例如：已经发布了 v1.0 v2.0 v3.0 三个版本，这个时候，我突然想不改现有代码的前提下，在 v2.0 的基础上加个新功能，作为 v4.0 发布。就可以检出 v2.0 的代码作为一个 branch ，然后作为开发分支

\section{补丁(patch)(未完成)}
\verb|git patch|功能，用于保存代码的变动，以一种可分发的、可重复使用的、文件的形式来保存给定的版本之间的变动。主要的行为包括，将2个给定的版本之间的差异生成为一个patch，将某个patch文件应用到当前的工作区。

\paragraph{2种patch格式以及对比}
git自身提供了2种patch的格式
\begin{itemize}
    \item 用\verb|git diff|生成的UNIX标准补丁\verb|.diff|文件
    \item 用\verb|git format-patch|生成的Git专用\verb|.patch|文件
\end{itemize}

他们有如下的区别
\begin{itemize}
    \item \verb|.diff|文件只是记录文件改变的内容，不带有commit记录信息，多个commit可以合并成一个diff文件。
    \item \verb|.patch|文件带有记录文件改变的内容，也带有commit记录信息，每个commit对应一个patch文件。
    \item 兼容性：\verb|git diff|生成的Patch兼容性强。如果你在修改的代码的官方版本库不是Git管理的版本库，那么你必须使用\verb|git diff|生成的patch才能让你的代码被项目的维护人接受。
    \item 除错功能：对于\verb|git diff|生成的patch，你可以用\verb|git apply --check| 查看补丁是否能够干净顺利地应用到当前分支中；如果\verb|git format-patch| 生成的补丁不能打到当前分支，\verb|git am|会给出提示，并协助你完成打补丁工作，你也可以使用\verb|git am -3|进行三方合并，详细的做法可以参考git手册或者Progit。从这一点上看，两者除错功能都很强。
    \item 版本库信息：由于\verb|git format-patch|生成的补丁中含有这个补丁开发者的名字，因此在应用补丁时，这个名字会被记录进版本库，显然，这样做是恰当的。因此，目前使用Git的开源社区往往建议大家使用format-patch生成补丁。
\end{itemize}

\section{其他命令}
\subsection{git rm}
\verb|git rm --cached <file>|：直接从暂存区删除文件，工作区则不做出改变

\subsection{git stash}
\paragraph{功能}
如果突然遇到紧急业务需要处理，可以使用\verb|git stash|命令将当前未提交的修改(即，工作区的修改和暂存区的修改)先暂时存储起来，待紧急事务处理完成之后，通过\verb|git stash pop|命令将之前存储的临时工作区和暂存区提取出来，继续进行新功能的开发工作。

在通过\verb|git stash pop|命令还原工作区之前，首先要回到暂存工作区所在的那个分支。

\verb|stash|是本地的，不会通过\verb|git push|命令上传到\verb|git server|上

\paragraph{不保存新增文件}
与当前\verb|HEAD|相比，新增的文件，是不会保存在\verb|git stash|当中的。在使用缓存功能时一定要注意，妥善处理这些新增文件，考虑是否需要彻底丢弃他们(无法找回)，还是先将他们暂存到一个\verb|commit|当中。

\paragraph{不保存分支信息}
\verb|git stash|是不会保存分支信息的，他只保存文件。使用\verb|git stash pop|或者\verb|apply|提取出上次保存的信息的时候，会直接应用到当前所在分支。

\paragraph{常用形式}
\begin{enumerate}
	\item \verb|git stash pop|用于取出最近一次储藏的修改到工作区，这个指令将缓存堆栈中的第一个\verb|stash|删除
	
	\item \verb|git stash list|查看存储的临时工作区
	
	\item \verb|git stash apply stash@{index}|将缓存堆栈中的\verb|stash|应用到工作目录中，但并不删除\verb|stash|拷贝。
	
	\item \verb|git stash drop stash@{index}|将缓存堆栈中的\verb|stash|移除。如果不带有\verb|stash@{index}|，则是删除最新的一条\verb|stash|
	
	\item \verb|git stash clear|清空缓存堆栈中的所有\verb|stash|
	
	\item \verb|git stash show|查看指定\verb|stash|的diff
	
	\item \verb|git stash save -m [comment]|添加缓存的同时，为之添加一个备注。如果不使用\verb|save|选项，则系统会添加默认的备注信息。
\end{enumerate}

\subsection{git reflog}
\paragraph{功能}
reflog, short for reference log

使用\verb|git log|命令只可以查看到HEAD指针及其之前的版本信息，如果版本发生过回退操作，则可能会出现，HEAD指针之后仍存在历史提交版本的情况，而这些提交版本信息通过\verb|git log|命令是看不到的。

我们可以通过使用\verb|git reflog|命令，就可查看到所有历史版本信息。由于查看所有历史版本信息的目的，大多是为了进行版本回退或恢复操作所使用，从中找到所需的commit索引。

\begin{figure}[h]\centering
	\caption{reflog与log的作用差别} \label{reflog与log的作用差别}
	\includegraphics[width=1.0\textwidth]{git_reflog.png}
\end{figure}

\paragraph{reflog属于本地仓库}

\verb|reflog|并不是Git仓库的一部分，它单独存储，它纯属是本地的。

\verb|git reflog|命令显示的内容，应该是存储在\verb|.git/logs/HEAD|文件中，或者是\verb|.git/logs/refs|目录中的文件。

也就是说\verb|git reflog|命令中保留了从\verb|clone|仓库开始，用户所有在本地库中的操作。

\paragraph{reflog的输出内容}

由上至下，分别是从新到旧。这个顺序与\verb|git log|是相同的。

\paragraph{常用形式}

\verb|git reflog show branchName|查看指定分支的reflog

\verb|git reflog show --all|查看所有分支的reflog

\chapter{cmake - 跨平台编译构建工具}
\minitoc
\section{什么是cmake}
\verb|cmake|是一个跨平台的安装(编译)工具，可以用简单的语句来描述所有平台的安装(编译过程)。

\verb|cmake|并不直接建构出最终的软件，而是产生标准的建构档(如 Unix 的 \verb|Makefile| 或 Windows Visual C++ 的 \verb|projects/workspaces|)，然后再依一般的建构方式来生成软件。

\verb|cmake|可以编译源代码、制作程序库、产生适配器(\verb|wrapper|)、还可以用任意的顺序建构执行档。

\verb|cmake|支持 in-place 建构(二进档和源代码在同一个目录树中)和 out-of-place 建构(二进档在别的目录里)，因此可以很容易从同一个源代码目录树中建构出多个二进档。\verb|CMake| 也支持静态与动态程式库的建构。

\verb|cmake|这个名字是“cross platform make”的缩写。虽然名字中含有的\verb|make|，但是CMake和Unix上常见的\verb|make|工具是分开的，而且\verb|cmake|比\verb|make|范围更广，后者是前者的一个子集。

\begin{figure}[htbp]\centering
    \caption{cmake在编译构建中的作用} \label{cmake structure}
    \includegraphics[width=1\textwidth]{cmake structure.png}
\end{figure}

\subsection{cmake的特点}
\paragraph{跨平台性}

你或许听过好几种 Make 工具，例如 GNU的\verb|Make| ，QT 的 \verb|qmake| ，微软的 \verb|MS nmake|，\verb|BSD Make|(pmake)，\verb|Makepp|，等等。这些 \verb|Make| 工具遵循着不同的规范和标准，所执行的 \verb|Makefile| 格式也千差万别。

这样就带来了一个严峻的问题：如果软件想跨平台，必须要保证能够在不同平台编译。而如果使用上面的 Make 工具，就得为每一种标准写一次 Makefile ，这将是一件让人抓狂的工作。

CMake 就是针对上面问题所设计的工具：它首先允许开发者编写一种平台无关的 CMakeList.txt 文件来定制整个编译流程，然后再根据目标用户的平台进一步生成所需的本地化 Makefile 和工程文件，如 Unix 的 Makefile 或 Windows 的 Visual Studio 工程。从而做到“Write once, run everywhere”。

显然，CMake 是一个比上述几种 make 更高级的编译配置工具

\subsection{什么是makefile}

\verb|makefile|定义了一系列的规则来指定哪些文件需要先编译，哪些文件需要后编译，哪些文件需要重新编译，甚至于进行更复杂的功能操作，因为 \verb|Makefile|就像一个Shell脚本一样，也可以执行操作系统的命令。

\verb|make|是一个命令工具，它解释\verb|makefile|中的指令。

在 UNIX 系统中，习惯使用Makefile作为 \verb|makefile| 文件

\section{cmake官方tutorial学习}

\subsection{step 1 - A Basic Starting Point}

Where do I start with CMake? This step will provide an introduction to some of CMake's basic syntax, commands, and variables. As these concepts are introduced, we will work through three exercises and create a simple CMake project.

如何开始使用cmake。本节将通过3个练习，来介绍cmake基础的语法、命令和变量。

Each exercise in this step will start with some background information. Then, a goal and list of helpful resources are provided. Each file in the Files to Edit section is in the Step1 directory and contains one or more TODO comments. Each TODO represents a line or two of code to change or add. The TODO s are intended to be completed in numerical order, first complete TODO 1 then TODO 2, etc. The Getting Started section will give some helpful hints and guide you through the exercise. Then the Build and Run section will walk step-by-step through how to build and test the exercise. Finally, at the end of each exercise the intended solution is discussed.

本节的3个练习所使用的文件都在step1文件夹中，每个练习由多条TODO任务组成，每个TODO任务需要你添加或者修改几行代码。TODO需要按照序号按顺序完成。

Also note that each step in the tutorial builds on the next. So, for example, the starting code for Step2 is the complete solution to Step1.

step之间是连续的，例如step2要基于step1所有任务完成后的代码继续进行。(但是代码包里也直接给出了每个step所开始的代码)



\paragraph{Exercise 1 - Building a Basic Project}

The most basic CMake project is an executable built from a single source code file.

cmake项目的基本目标就是从源代码构建出可执行文件。

Note: Although upper, lower and mixed case commands are supported by CMake, lower case commands are preferred and will be used throughout the tutorial.

cmake是不区分大小写的，但是建议cmake的所有命令，都使用小写。

Any project's top most CMakeLists.txt must start by specifying a minimum CMake version using the \verb|cmake_minimum_required()| command. This establishes policy settings and ensures that the following CMake functions are run with a compatible version of CMake.

项目最顶层的CMakeLists.txt，必须开始于\verb|cmake_minimum_required()|命令，用于设置所使用的cmake程序的最小版本，保证代码的构建过程是设计者可控的，避免由于使用过于旧版本的cmake程序而导致其他意想不到的后果。

To start a project, we use the \verb|project()| command to set the project name. This call is required with every project and should be called soon after \verb|cmake_minimum_required()|. As we will see later, this command can also be used to specify other project level information such as the language or version number.

使用\verb|project()|命令来设置项目的名称。每一个工程都需要使用这个命令，而且要在\verb|cmake_minimum_required()|命令之后调用。这个命令也可以设置这个工程的版本号，或者所使用的程序语言(但是一般只有C和CXX两种)

Finally, the \verb|add_executable()| command tells CMake to create an executable using the specified source code files.

\verb|add_executable()|命令，将源代码文件编译构建成一个可执行文件。

\paragraph{添加编译选项}
以下几种方式都可以添加编译选项

\begin{lstlisting}
add_compile_options(-Wall -Werror -Wstrict-prototypes -Wmissing-prototypes)
set(CMAKE_C_FLAGS "-Wall -Werror -Wstrict-prototypes -Wmissing-prototypes)
\end{lstlisting}

\verb|CMAKE_CXX_FLAGS|是针对C++编译器的参数选项，默认保存环境变量\verb|CXX_FLAGS|的内容，但是如果直接修改这个参数值，那么系统会忽略原\verb|CXX_FLAGS|的内容。

\verb|add_compile_options()|的作用域是整个项目的，对所有的编译器都起效。而通过\verb|CMAKE_CXX_FLAGS|变量来设置，只对C++编译器有效，作用域是单个编译文件。

综上，对于一些在整个项目中通用的编译选项可以使用\verb|add_compile_options|命令来添加比较方便，对于各个模块中的独立选项则使用\verb|CMAKE_CXX_FLAGS|变量更好。

\paragraph{生成makefile与构建}

在需要生成makefile的目录下运行\verb|cmake|命令。

一般是在项目中创建一个\verb|build|文件夹

\verb|cmake [filename]|，将cmake文件所在的目录传入给\verb|cmake|命令，如果当前就在\verb|build|文件夹下，那么就直接运行\verb|cmake .|即可

生成makefile之后，在该目录下运行\verb|make|即可

上述过程也可以通过\verb|cmake --build [dir]|来一步完成。

如果当前目录下已经有了\verb|CMakeCache.txt|缓存文件，则无须再给出cmake文件所在的目录，直接给出当前目录即可。

\paragraph{小结}

\verb|cmake_minimum_required()|命令，设置cmake的最低版本

\verb|cmake_minimum_required(VERSION 3.10)|，\verb|VERSION|要大写

\verb|project()|命令，创建一个项目，例如\verb|project(Tutorial)|

\verb|add_executable()|，编译一个可执行文件，例如\verb|add_executable(Tutorial tutorial.cpp)|

\paragraph{Exercise 2 - Specifying the C++ Standard}

cmake有一个自带的变量，以\verb|CMAKE_|开头。

自己创建变量的时候，要注意与这些\verb|CMAKE_|开头的变量做区分，避免重名。

\verb|CMAKE_CXX_STANDARD|

\verb|CMAKE_CXX_STANDARD_REQUIRED|

通过\verb|set()|命令，设置C++标准，例如\verb|set(CMAKE_CXX_STANDARD 11)|，目前支持的有98，11，14，17，20，23，26

在C++源代码中，可以通过输出一个宏\verb|__cplusplus|来看当前编译器使用的标准

\verb|CMAKE_CXX_STANDARD_REQUIRED|如果为true，则\verb|CMAKE_CXX_STANDARD|规定的标准如果不满足，则编译无法通过。即，\verb|CMAKE_CXX_STANDARD_REQUIRED|用于记录编译器是否需要对C++标准进行判断。

\paragraph{Exercise 3 - Adding a Version Number and Configured Header File}

\subparagraph{message()命令}

\verb|message()|的作用是向控制台输出信息。第一个参数可以传入一个mode(比如\verb|DEBUG|或者\verb|STATUS|等)，用于区分这条输出消息的重要程度，或者分类。

\subparagraph{cmake中访问变量}

cmake文件中访问某个变量，格式为\verb|${variable_name}|

\verb|<PROJECT-NAME>_VERSION_MAJOR|，通过\verb|project()|命令设置的版本号的第一个数字，其中\verb|<PROJECT-NAME>|要替换成\verb|project()|命令设置的项目名。假如项目名为Tutorial，则完整的写法应该是\verb|${Tutorial_VERSION_MAJOR}|

\verb|<PROJECT-NAME>_VERSION_MINOR|，同上，第二个数字

\verb|project()|命令设置版本号的方式，\verb|project(Tutorial VERSION 1.0)|

在CMakeLists.txt中写的版本号，如何在C/C++源代码中能够使用到这个版本号的数值。原理是，cmake通过某个定义好的脚本(*.h.in文件)，生成一个*.h头文件，供该项目以头文件的方式使用，这个头文件里定义了宏变量，来存储版本号。 

\verb|configure_file(TutorialConfig.h.in TutorialConfig.h)|

在cmake的目录下先创建一个模板文件\verb|TutorialConfig.h.in|，通过\verb|configure_file()|命令来生成

模板文件\verb|TutorialConfig.h.in|的内容如下

\begin{verbatim}
    // TutorialConfig.h.in
    
    // the configured options and settings for Tutorial
    #define Tutorial_VERSION_MAJOR @Tutorial_VERSION_MAJOR@
    #define Tutorial_VERSION_MINOR @Tutorial_VERSION_MINOR@
\end{verbatim}

用\verb|@|括起来变量名，也可以访问cmake的变量

如果变量中包含了空格，则可以在调用变量名的时候在外面加一层双引号，以防变量含有空格的时候造成错误，比如\verb|"${PROJECT_BINARY_DIR}"|

在源文件中就可以添加头文件\verb|#include "TutorialConfig.h"|

但是这样情况下，生成的头文件\verb|TutorialConfig.h|是在\verb|build|文件夹下的，并不在项目源代码文件夹下，所以还要使用\verb|target_include_directories()|命令来添加头文件的搜索路径。

\verb|target_include_directories(Tutorial PUBLIC "${PROJECT_BINARY_DIR}")|

\subparagraph{cmakedefine和define的区别}

\verb|cmakedefine|和\verb|define|是使用在模板文件\verb|TutorialConfig.h.in|中的。

\verb|cmakedefine|和\verb|define|在非零的时候，效果是一样的，如果是0，\verb|cmakedefine|不会生成宏变量，而\verb|define|仍然会正常生成。

对于bool型变量，为false时，cmakedefine不会定义，而define会正常生成宏变量。

\verb|option()|命令，可以定义一个bool型标志位，使用方式为\verb|option(<variable> "<help_text>" [value])|，如果没有初值，则为false。

\subsection{Step 2: Adding a Library}

\paragraph{Exercise 1 - Creating a Library}

\verb|add_library()|命令，可以创建一个库(而非是executable)，第一个参数是库名称，第二个参数是源文件。

如果在子目录中的CMakeLists.txt文件中，只需要使用一次\verb|add_library()|命令即可。而在根目录中的CMakeLists.txt文件中，则要将子目录的CMakeLists.txt文件包含进来，使用\verb|add_subdirectory()|命令，参数是文件夹的名称。

库的头文件，也要通过\verb|target_include_directories()|命令添加，以便编译器能够搜索到该头文件。

使用\verb|target_link_libraries()|命令，将可执行文件与库之间建立链接(也可以说，是将库链接到可执行文件上)，例如\verb|target_link_libraries(Tutorial PUBLIC MathFunctions)|。

可以使用链接选项\verb|PUBLIC|等，这里暂不介绍。
`
\paragraph{Exercise 2 - Adding an Option}

\verb|option()|命令，可以生成一个标志位。

\begin{verbatim}
// TODO 7
option(USE_MYMATH "Use tutorial provided math implementation" ON)
\end{verbatim}

第一个参数是标志位的名称，第二个参数是一个说明字段，说明这个标志位的功能，第三个参数是这个标志位的值，如果不给出值，则默认值取OFF。

\verb|if()|结构，判断分支。

根据一个标志位，来判断编译哪一个库。

\begin{verbatim}
// TODO 8
if (USE_MYMATH)
    target_compile_definitions(MathFunctions PRIVATE "USE_MYMATH")
endif()
\end{verbatim}

当\verb|USE_MYMATH|为ON的时候，编译选项\verb|USE_MYMATH|会启用。

我们就可以在源代码中，通过预编译命令，来启用某一块代码块，或者不编译某一块代码块。

\begin{verbatim}
// TODO 9
#ifdef USE_MYMATH
    return detail::mysqrt(x);
#else
    return std::sqrt(x);
#endif 
\end{verbatim}

如果宏\verb|USE_MYMATH|是已定义的，则引入头文件\verb|mysqrt.h|

\begin{verbatim}
// TODO 10
#ifdef USE_MYMATH
#  include "mysqrt.h"
#endif
\end{verbatim}

引入头文件\verb|cmath|，因为现在我们需要使用标准库给出的开平方的函数\verb|std::sqrt|

\begin{verbatim}
// TODO 11
#include <cmath>
\end{verbatim}

根据前文的设置，如果\verb|USE_MYMATH|为OFF的时候，\verb|mysqrt.cxx|也会被编译到\verb|MathFunctions|target中。

因为根据TODO1，前文写的是\verb|add_library(MathFunctions MathFunctions.cxx mysqrt.cxx)|

在\verb|USE_MYMATH|启用的代码块中，新增一个额外的库\verb|SqrtLibrary |，用于管理这个单独的编译需求。

\begin{verbatim}
// TODO 12
add_library(SqrtLibrary STATIC mysqrt.cxx)
\end{verbatim}

\begin{verbatim}
// TODO 13    
target_link_libraries(MathFunctions PUBLIC SqrtLibrary)
\end{verbatim}

可以修改之前的\verb|add_library()|命令，删去\verb|mysqrt.cxx|

\begin{verbatim}
// TODO 14
add_library(MathFunctions MathFunctions.cxx)
\end{verbatim}

\subsection{Step 3: Adding Usage Requirements for a Library}

\paragraph{Exercise 1 - Adding Usage Requirements for a Library}

任何连接到\verb|MathFunctions|库的目标，都需要将当前文件夹(指的是\verb|MathFunctions|的文件夹)添加到头文件搜索目录，但是\verb|MathFunctions|库自身不需要如此。这就需要使用\verb|INTERFACE|选项。

Remember \verb|INTERFACE| means things that consumers require but the producer doesn't.

在MathFunctions文件夹中的\verb|CMakeLists.txt|中添加如下代码。

\begin{verbatim}
// TODO 1
target_include_directories(MathFunctions INTERFACE ${CMAKE_CURRENT_SOURCE_DIR})
\end{verbatim}

因此，我们在外层的\verb|CMakeLists.txt|中就不需要手动添加\verb|MathFunctions|的文件夹添加到头文件搜索路径了。因此，修改代码如下TODO 2和3。

\begin{verbatim}
// TODO 2
list(APPEND EXTRA_INCLUDES "${PROJECT_SOURCE_DIR}/MathFunctions")
\end{verbatim}

\begin{verbatim}
// TODO 3
target_include_directories(Tutorial PUBLIC "${PROJECT_BINARY_DIR}")
\end{verbatim}

\paragraph{Exercise 2 - Setting the C++ Standard with Interface Libraries}

首先，移除之前编写的如下代码。

我们现在要使用接口库(interface library)的形式来管理C++编译需求。

\begin{verbatim}
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED True)
\end{verbatim}

创建一个接口库名为\verb|tutorial_compiler_flags|，并且使用命令\verb|target_compile_features()|来添加编译特征(compile feature)\verb|cxx_std_11|

\begin{verbatim}
// TODO 4
add_library(tutorial_compiler_flags INTERFACE)
target_compile_features(tutorial_compiler_flags INTERFACE cxx_std_11)
\end{verbatim}

在根目录\verb|CMakeLists.txt|中添加如下代码

\begin{verbatim}
// TODO 5
target_link_libraries(Tutorial PUBLIC MathFunctions tutorial_compiler_flags)
\end{verbatim}

在\verb|MathFunctions|文件夹的\verb|CMakeLists.txt|中添加如下代码

\begin{verbatim}
// TODO 6
target_link_libraries(SqrtLibrary PUBLIC tutorial_compiler_flags)
// TODO 7
target_link_libraries(MathFunctions PUBLIC SqrtLibrary)
\end{verbatim}

这样，我们当前工程，仍然维持了在C++11版本以上的条件下才进行编译，但是是更换了另外一种写法。

\subsection{Step 4: Adding Generator Expressions}

\paragraph{生成器表达式(generator expression)}

生成器表达式(generator expression)功能在cmake3.15版本之后才有。

cmake的两个过程，配置阶段，生成阶段。

\verb|message()|命令作用于第一个阶段。

生成器表达式作用于第二个阶段。

一般格式为\verb|$<statements>|，生成器表达式包含许多种功能，逻辑判断、查询、输出等。

\verb|$<condition : true_string>|，当\verb|condition|为true时，该变量为\verb|true_string|，为false时，为空。

\verb|$<COMPILE_LANG_AND_ID:language,compiler_ids>|，\verb|language|是C或者CXX，\verb|compiler_ids|是编译器的名称，可以有多个参数。这个生成器表达式的含义是，当当前构建环境使用的是指定语言和制定编译器列表中的某一种的时候，则为1，否则为0。

举例。

\begin{verbatim}
    set(gcc_like_cxx "$<COMPILE_LANG_AND_ID:CXX,ARMClang,AppleClang,Clang,GNU,LCC>")
    set(msvc_cxx "$<COMPILE_LANG_AND_ID:CXX,MSVC>")
    
    target_compile_options(tutorial_compiler_flags INTERFACE
    "$<${gcc_like_cxx}:-Wall;-Wextra;-Wshadow;-Wformat=2;-Wunused>"
    "$<${msvc_cxx}:-W3>"
    )
    
    target_compile_options(tutorial_compiler_flags INTERFACE
    "$<${gcc_like_cxx}:$<BUILD_INTERFACE:-Wall;-Wextra;-Wshadow;-Wformat=2;-Wunused>>"
    "$<${msvc_cxx}:$<BUILD_INTERFACE:-W3>>"
    )
\end{verbatim}

调整cmake最低版本需求为3.15

\begin{verbatim}
// TODO 1
cmake_minimum_required(VERSION 3.15)
\end{verbatim}

创建两个变量，使用生成器表达式来给他们赋值

\begin{verbatim}
// TODO 2
set(gcc_like_cxx "$<COMPILE_LANG_AND_ID:CXX,ARMClang,AppleClang,Clang,GNU,LCC>")
set(msvc_cxx "$<COMPILE_LANG_AND_ID:CXX,MSVC>")
\end{verbatim}

在编译选项中，调整接口库\verb|tutorial_compiler_flags|的选项。

\begin{verbatim}
// TODO 3
target_compile_options(tutorial_compiler_flags INTERFACE
    "$<${gcc_like_cxx}:-Wall;-Wextra;-Wshadow;-Wformat=2;-Wunused>"
    "$<${msvc_cxx}:-W3>"
    )
\end{verbatim}

使用生成器表达式\verb|$<BUILD_INTERFACE:...>|

Content of \verb|...| when the property is exported using \verb|export()|, or when the target is used by another target in the same buildsystem. Expands to the empty string otherwise.

当当前target被同一环境内其他target所使用的时候，该生成器表达式的值为\verb|...|，否，则为空。

\begin{verbatim}
target_compile_options(tutorial_compiler_flags INTERFACE
    "$<${gcc_like_cxx}:$<BUILD_INTERFACE:-Wall;-Wextra;-Wshadow;-Wformat=2;-Wunused>>"
    "$<${msvc_cxx}:$<BUILD_INTERFACE:-W3>>"
    )
\end{verbatim}

\subsection{Step 5: Installing and Testing}

我们已经可以使用cmake来构建可执行文件了。现在要配置这个可执行文件的安装过程。

使用\verb|install()|命令来配置安装选项。

step 5的目标是安装可执行文件Tutorial和MathFunctions库

\paragraph{Exercise 1 - Install Rules}

使用cmake的\verb|--install|选项进行安装。运行\verb|cmake --install [dir]|命令，安装到传入的路径下。

安装的路径\verb|${CMAKE_INSTALL_PREFIX}|

对于step 5这个工程，安装包含两个部分：

\begin{itemize}
\item 对于MathFunctions库，需要将库文件安装到lib目录，将头文件安装到include目录
\item 对于可执行文件Tutorial，需要将可执行文件安装到bin目录，将头文件安装到include目录
\end{itemize}

在\verb|MathFunctions|文件夹的\verb|CMakeLists.txt|中添加如下代码

\begin{verbatim}
// TODO 1
set(installable_libs MathFunctions tutorial_compiler_flags)
if(TARGET SqrtLibrary)
    list(APPEND installable_libs SqrtLibrary)
endif()
install(TARGETS ${installable_libs} DESTINATION lib)
\end{verbatim}

\begin{verbatim}
// TODO 2
install(FILES MathFunctions.h DESTINATION include)
\end{verbatim}

在根目录的\verb|CMakeLists.txt|中添加如下代码

\begin{verbatim}
// TODO 3 4
install(TARGETS Tutorial DESTINATION bin)
install(FILES "${PROJECT_BINARY_DIR}/TutorialConfig.h"
    DESTINATION include
    )
\end{verbatim}

\paragraph{Exercise 2 - Testing Support}

使用\verb|ctest|命令来运行测试

运行ctest可执行文件\verb|ctest -N|或者\verb|ctest -VV|

开启测试功能，\verb|enable_testing()|命令

\begin{verbatim}
// TODO 5
enable_testing()
\end{verbatim}

\verb|add_test()|命令，可以添加测试任务

\verb|COMMAND|选项后面传入的命令是在终端上运行的命令，例如TODO 6中，就是运行可执行文件Tutorial，并且传入一个参数25

只是检测程序能够顺利运行，并不检查其结果，只确保不会发生segmentation fault等崩溃现象。

\begin{verbatim}
// TODO 6
add_test(NAME Runs COMMAND Tutorial 25)
\end{verbatim}

\verb|PASS_REGULAR_EXPRESSION|选项可以校验程序的输出，是否含有某些字符串

\begin{verbatim}
// TODO 7
add_test(NAME Usage COMMAND Tutorial)
set_tests_properties(Usage
    PROPERTIES PASS_REGULAR_EXPRESSION "Usage:.*number"
    )
\end{verbatim}

校验结果是否正确，本程序的功能是计算平方根。

\begin{verbatim}
// TODO 8
add_test(NAME StandardUse COMMAND Tutorial 4)
set_tests_properties(StandardUse
    PROPERTIES PASS_REGULAR_EXPRESSION "4 is 2"
    )
\end{verbatim}

增加更多的测试。

使用\verb|function()|命令创建一个函数，可以快速创建更多测试。

\begin{verbatim}
// TODO 9
function(do_test target arg result)
    add_test(NAME Comp${arg} COMMAND ${target} ${arg})
    set_tests_properties(Comp${arg}
        PROPERTIES PASS_REGULAR_EXPRESSION ${result}
        )
endfunction()

# do a bunch of result based tests
do_test(Tutorial 4 "4 is 2")
do_test(Tutorial 9 "9 is 3")
do_test(Tutorial 5 "5 is 2.236")
do_test(Tutorial 7 "7 is 2.645")
do_test(Tutorial 25 "25 is 5")
do_test(Tutorial -25 "-25 is (-nan|nan|0)")
do_test(Tutorial 0.0001 "0.0001 is 0.01")
\end{verbatim}

\subsection{Step 6: Adding Support for a Testing Dashboard}

使用CDash工具可以用友好的图表形式将测试结果显示出来

在根目录下












\subsection{Step 7: Adding System Introspection}

\paragraph{系统特性检测}

引入CheckCXXSourceCompiles模块

在CMakeLists.txt中添加如下语句\verb|include(CheckCXXSourceCompiles)|

添加如下语句，用于检测当前项目中是否有\verb|log()|和\verb|exp()|函数，将检测的结果，保存到\verb|HAVE_LOG|和\verb|HAVE_EXP|中

\begin{verbatim}
check_cxx_source_compiles("
#include <cmath>
int main() {
    std::log(1.0);
    return 0;
}
" HAVE_LOG)
check_cxx_source_compiles("
#include <cmath>
int main() {
    std::exp(1.0);
    return 0;
}
" HAVE_EXP)
\end{verbatim}

使用\verb|target_compile_definitions()|命令来添加编译选项，如果不存在，则去使用另一种库

\begin{verbatim}
if(HAVE_LOG AND HAVE_EXP)
    target_compile_definitions(SqrtLibrary
    PRIVATE "HAVE_LOG" "HAVE_EXP"
    )
endif()

    target_link_libraries(MathFunctions PRIVATE SqrtLibrary)
endif()
\end{verbatim}

\paragraph{生成静态库或者动态库}

\verb|add_library()|命令的第二个参数，可以传入\verb|STATIS|、\verb|SHARED|或者\verb|MODULE|

设置生成的库文件输出的目录

\begin{verbatim}
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}")
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}")
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}")
\end{verbatim}

\section{cmake基本语法}

cmake命令对大小写不敏感(case insensitivity)

但是cmake中的变量是区分大小写的，包括内置变量

注释以井字符\verb|#|开始

参数之间用空格分隔，而不用逗号\verb|,|

\section{内置变量}
\begin{enumerate}
    \item \verb|PROJECT_SOURCE_DIR|和\verb|CMAKE_SOURCE_DIR|，代表最顶层的cmake文件的路径。区别在于，\verb|PROJECT_SOURCE_DIR|仅限于当前的project，也就是带有\verb|project()|所限定了的project范围内。
    \item \verb|PROJECT_BINARY_DIR|、\verb|CMAKE_BINARY_DIR|和\verb|_BINARY_DIR|，运行cmake命令的目录，即工程编译发生的路径。
    \item \verb|CMAKE_CURRENT_SOURCE_DIR|，当前处理的CMakeLists.txt文件所在的目录
    \item \verb|CMAKE_CURRENT_LIST_DIR|，这是当前正在处理的列表文件的目录
    \item \verb|CMAKE_BUILD_TYPE|，控制构建类型，默认值为\verb|Debug|
    
    \verb|CMAKE_XXX_DIR|和\verb|PROJECT_XXX_DIR|的区别
    \begin{itemize}
        \item 当存在子模块时，它们就会开始产生区别。
        \item \verb|PROJECT_SOURCE_DIR| 表示最近一次调用 \verb|project| 的 CMakeLists.txt 所在的源码目录。
        \item \verb|CMAKE_CURRENT_SOURCE_DIR| 表示当前 CMakeLists.txt 所在的源码目录。
        \item \verb|CMAKE_SOURCE_DIR| 表示最外层CMakeLists.txt 的源码根目录。
        \item 利用 \verb|PROJECT_SOURCE_DIR| 可以实现从子模块里直接获得项目最外层目录的路径。
        \item 不建议使用 \verb|CMAKE_SOURCE_DIR|，那样会让你的项目无法被人作为子模块使用。
    \end{itemize}    
    
    \item \verb|EXECUTABLE_OUTPUT_PATH|，可执行文件的输出路径
    \item \verb|LIBRARY_OUTPUT_PATH|，库文件的输出路径
    \begin{itemize}
        \item \verb|Debug|调试模式，相当于\verb|-O0 -g|，完全不优化，生成调试信息，方便调试程序
        \item \verb|Release|发布模式，相当于\verb|-O3 -DNDEBUG|，优化程度最高，性能最佳，但是编译比\verb|Debug|慢
        \item \verb|MinSizeRel|最小体积发布，相当于\verb|-Os -DNDEBUG|，生成的文件比 \verb|Release| 更小，不完全优化，减少二进制体积
        \item \verb|RelWithDebInfo|带调试信息发布，相当于\verb|-O2 -g -DNDEBUG|，生成的文件比 \verb|Release| 更大，因为带有调试的符号信息
        这是为了让用户在使用程序出错时，能够反馈一些信息。
    \end{itemize}
    \verb|NDEBUG|宏会使\verb|assert|宏被去除掉(\verb|assert|宏主要是在调试时使用)
    
    由于在 CMake 默认情况下， \verb|CMAKE_BUILD_TYPE| 是一个空字符串。因此可以通过 \verb|if (NOT CMAKE_BUILD_TYPE)|判断是否为空来判断用户是否指定了 \verb|CMAKE_BUILD_TYPE|，如果空则自动设为 \verb|Release| 模式。
    
    \begin{lstlisting}
if (NOT CMAKE_BUILD_TYPE)
    set(CMAKE_BUILD_TYPE Release)
endif()        
    \end{lstlisting}
\end{enumerate}

\section{设置命令}

\subsection{project()}

\paragraph{格式}

\verb|project(<PROJECT-NAME> [<language-name>...])|

\paragraph{功能}

设置该工程的名称

将当前的\verb|CMakeLists.txt|文件当做工程的根目录。

\verb|PROJECT_SOURCE_DIR|，最后一次调用\verb|project()|命令的目录。

子模块里也可以运行\verb|project()|命令，将当前目录作为一个独立的子项目，类似地，\verb|PROJECT_SOURCE_DIR|也就会因此而改变。

\subsection{cmake\_minimum\_required()}

\paragraph{格式}

\verb|cmake_minimum_required(VERSION <min>[...<policy_max>] [FATAL_ERROR])|

\paragraph{功能}

设置一个对cmake最低版本号的需求

\subsection{set()}

\paragraph{格式}

\verb|set(<variable> <value>... [PARENT_SCOPE])|

\paragraph{功能}

\begin{enumerate}
    \item 定义一个变量，可以设置普通变量，缓存变量和环境变量。Set a normal, cache, or environment variable to a given value.
    \item 如果变量的值由多个个体组成，即\verb|<value>|部分有好多个个体，则会形成分号分割的列表(a semicolon-separated list)保存到对应变量中。
    \item 如果\verb|<value>|部分未空，则该变量会变成空，或者说，未设置的状态。
\end{enumerate}

\subsection{set\_target\_properties()}
设置一个属性的值

\paragraph{基本语法}
\begin{lstlisting}
set_target_properties(目标文件1 目标文件2 ...
                        PROPERTIES 
                        属性1 属性值1 属性2 属性值2 ...)                 
\end{lstlisting}

举例比如\verb|set_target_properties(libhello PROPERTIES OUTPUT_NAME "hello")|，就是对\verb|libhello|目标的\verb|OUTPUT_NAME|属性的值设置为\verb|"hello"|。

\subsubsection{常用的属性}
\verb|OUTPUT_NAME|，设置目标文件的输出名称

\verb|CMAKE_RUNTIME_OUTPUT_DIRECTORY|，指定生成的可执行文件（二进制文件）的输出目录

\verb|CMAKE_LIBRARY_OUTPUT_DIRECTORY|，指定生成的动态库（共享库）的输出目录

\verb|CMAKE_ARCHIVE_OUTPUT_DIRECTORY|，指定生成的静态库（归档文件）的输出目录

\verb|RUNTIME_OUTPUT_DIRECTORY|，二进制执行文件

\verb|LIBRARY_OUTPUT_DIRECTORY|，动态库

\verb|ARCHIVE_OUTPUT_DIRECTORY|，静态库

\verb|DEBUG_POSTFIX|，指定Debug模式下目标文件名的后缀。

为了区别不同模式下的不同文件，我们可以指定Debug模式下的目标文件名后缀为 \verb|_d|，以用于区分 release 模式下生成的目标文件。
\begin{lstlisting}
SET_TARGET_PROPERTIES (<target> PROPERTIES DEBUG_POSTFIX <suffix_name>)

# 指定debug模式下的目标文件名后缀为 _d，即如果是动态库文件，那就是 libhello_d.so
SET_TARGET_PROPERTIES (hello PROPERTIES DEBUG_POSTFIX _d)
\end{lstlisting}

\subsubsection{创建自定义的属性}
\begin{lstlisting}
# 为目标文件 test_lib 创造一个 _STATUS_ 属性，属性值为 shared
set_target_properties(test_lib PROPERTIES _STATUS_ shared)
\end{lstlisting}

一次创建多个属性
\begin{lstlisting}
# 为目标 test_lib 创建属性 _STATUS_ ，并赋值为 shared 
# 为目标 test_lib_static 创建属性 _STATUS_STATIC_，并赋值为 static
set_target_properties(test_lib test_lib_static
                        PROPERTIES 
                        _STATUS_ shared 
                        _STATUS_STATIC_ static
)
\end{lstlisting}

\subsection{get\_target\_properties()}
使用\verb|get_target_properties|可以获取\verb|set_target_properties()|为某个目标\verb|target|设置的\verb|target_property|属性的值，并保存到变量\verb|variable|中。

\begin{lstlisting}
get_target_property(<variable> <target> <target_property>)
\end{lstlisting}

\subsection{aux\_source\_directory()}

格式：\verb|aux_source_directory([dir] [variable])|

收集所有在指定路径下的源文件的文件名，并将输出结果列表存储在制定的变量中。

\subsection{file()}

\begin{enumerate}
    
\item \verb|file(READ)|

    功能：读文件操作
    
    格式：\verb|file(READ <filename> <variable> [OFFSET <offset>] [LIMIT <max-in>] [HEX])|
    
    \begin{itemize}
        \item filename: 必选项 为要读取的文件，可以带绝对路径
        \item variable: 必选项，将文件内容读取到varible变量中。
        \item OFFSET <offset>：可选项，从文件中偏移位置offset 开始读取文件内容
        \item HEX：可选项，可以将读取文件内容转换成十六进制。
    \end{itemize}

\item \verb|file(GLOB_RECURSE)|

    功能：
    
    \begin{itemize}
        \item \verb|GLOB_RECURSE|选项将会生成一个类似于通常的\verb|GLOB|选项的list，只是它会寻访所有那些匹配目录的子路径并同时匹配查询表达式的文件。
        \item \verb|GLOB|只会查询下一级目录，而\verb|GLOB_RECURSE|会递归查找子目录
        \item 作为符号链接的子路径只有在给定\verb|FOLLOW_SYMLINKS|选项或者cmake策略CMP0009被设置为\verb|NEW|时，才会被寻访到。
        \item 参见\verb|cmake --help-policy CMP0009| 查询更多有用的信息
    \end{itemize}

\end{enumerate}

\section{指定命令}

\subsection{target\_include\_directories()}

功能：指定目标包含的头文件路径

格式：

\verb/target...ries(<target> [SYSTEM] [BEFORE] <PARAM> [items1...] [<PARAM> [items2...] ...])/

参数：格式中\verb|<PARAM>|部分，可以填写下面几个参数

\begin{itemize}
    \item \verb|PRIVATE|
    \item \verb|INTERFACE|
    \item \verb|PUBLIC|
\end{itemize}

\subsection{target\_link\_libraries}

功能：指定目标链接的库

与\verb|include_directories()|的功能基本一致

但是从保持形式风格统一的角度来说，使用\verb|target_link_libraries()|更好。

\subsection{target\_compile\_options()}

功能：指定目标的编译选项

\subsection{PRIVATE、INTERFACE和PUBLIC用法说明}

\paragraph{示例工程的目录结构}

我们以如下的工程为例讲解一下本节的命令的使用，测试工程目录结构如下

\begin{verbatim}
    cmake-test/                 工程主目录，main.c 调用 libhello-world.so
    ├── CMakeLists.txt
    ├── hello-world             生成 libhello-world.so，调用 libhello.so 和 libworld.so
    │   ├── CMakeLists.txt
    │   ├── hello               生成 libhello.so 
    │   │   ├── CMakeLists.txt
    │   │   ├── hello.c
    │   │   └── hello.h         libhello.so 对外的头文件
    │   ├── hello_world.c
    │   ├── hello_world.h       libhello-world.so 对外的头文件
    │   └── world               生成 libworld.so
    │       ├── CMakeLists.txt
    │       ├── world.c
    │       └── world.h         libworld.so 对外的头文件
    └── main.c
\end{verbatim}

\paragraph{调用关系}

\begin{verbatim}
    ├────libhello.so
    可执行文件────libhello-world.so
    ├────libworld.so
\end{verbatim}

\paragraph{PRIVATE}

私有的。生成 libhello-world.so时，只在 hello\_world.c 中包含了 hello.h，libhello-world.so 对外的头文件——hello\_world.h 中不包含 hello.h。而且 main.c 不会调用 hello.c 中的函数，或者说 main.c 不知道 hello.c 的存在，那么在 hello-world/CMakeLists.txt 中应该写入

\begin{verbatim}
    target_link_libraries(hello-world PRIVATE hello)
    target_include_directories(hello-world PRIVATE hello)
\end{verbatim}

\paragraph{INTERFACE}

接口。生成 libhello-world.so 时，只在libhello-world.so 对外的头文件——hello\_world.h 中包含 了 hello.h， hello\_world.c 中不包含 hello.h，即 libhello-world.so 不使用 libhello.so 提供的功能，只使用 hello.h 中的某些信息，比如结构体。但是 main.c 需要使用 libhello.so 中的功能。那么在 hello-world/CMakeLists.txt 中应该写入

\begin{verbatim}
    target_link_libraries(hello-world INTERFACE hello)
    target_include_directories(hello-world INTERFACE hello)
\end{verbatim}

\paragraph{PUBLIC}

公开的。PUBLIC = PRIVATE + INTERFACE。生成 libhello-world.so 时，在 hello\_world.c 和 hello\_world.h 中都包含了 hello.h。并且 main.c 中也需要使用 libhello.so 提供的功能。那么在 hello-world/CMakeLists.txt 中应该写入：

\begin{verbatim}
    target_link_libraries(hello-world PUBLIC hello)
    target_include_directories(hello-world PUBLIC hello)
\end{verbatim}

实际上，这三个关键字指定的是目标文件依赖项的使用范围(scope)或者一种传递(propagate)。官方说明

可执行文件依赖 libhello-world.so， libhello-world.so 依赖 libhello.so 和 libworld.so。

\begin{itemize}
    \item main.c 不使用 libhello.so 的任何功能，因此 libhello-world.so 不需要将其依赖—— libhello.so 传递给 main.c，hello-world/CMakeLists.txt 中使用 PRIVATE 关键字；
    \item main.c 使用 libhello.so 的功能，但是libhello-world.so 不使用，hello-world/CMakeLists.txt 中使用 INTERFACE 关键字；
    \item main.c 和 libhello-world.so 都使用 libhello.so 的功能，hello-world/CMakeLists.txt 中使用 PUBLIC 关键字；
\end{itemize}

\section{生成命令}

\subsection{add\_library()}

\paragraph{格式}

\verb/add_library(<name> [STATIC|SHARED|MODULE] [EXCLUDE_FROM_ALL] [<source>...])/

\paragraph{功能}

\begin{enumerate}
    \item 由命令给出的源代码，编译一个库对象(library target)
    
    \begin{itemize}
        \item 库对象的文件名必须是唯一的
        \item 根据平台的不同吗，库对象的文件名可能为\verb|lib<name>.a|或者\verb|<name>.lib|
    \end{itemize}

    \item 根据传入的第二个参数情况，生成不同类型的库对象，详见参数
\end{enumerate}

\paragraph{参数}

\begin{enumerate}
    \item \verb/STATIC|SHARED|MODULE/
    
    \begin{itemize}
        \item \verb|STATIC| libraries are archives of object files for use when linking other targets.
        \item \verb|SHARED| libraries are linked dynamically and loaded at runtime.
        \item \verb|MODULE| libraries are plugins that are not linked into other targets but may be loaded dynamically at runtime using dlopen-like functionality.
    \end{itemize}
    
    \item \verb|EXCLUDE_FROM_ALL|
    
    当函数中使用了\verb|EXCLUDE_FROM_ALL|参数之后，这个target就不会在\verb|make|的时候做任何操作，等于说是暂时屏蔽掉了。
\end{enumerate}

\paragraph{Object Libraries}

\verb|add_library(<name> OBJECT [<source>...])|

创建一个Object Library(目标库)

An object library compiles source files but does not archive or link their object files into a library.

什么是object library呢？The object library type defines a non-archival collection of object files resulting from compiling the given source files.

\subsection{add\_executable()}

\paragraph{格式}

\verb|add_executable(<name> [WIN32] [MACOSX_BUNDLE] [EXCLUDE_FROM_ALL] [source1] [source2 ...])|

\paragraph{功能}

\begin{enumerate}
    \item 编译构建成一个二进制文件
    
    \begin{itemize}
        \item 二进制文件名为\verb|<name>|，这个文件名在这个项目中必须是唯一的
        \item 根据平台的不同，编译后的可执行文件名可能为\verb|<name>.exe|或者\verb|<name>|
    \end{itemize}

    \item 输出路径
    
    \begin{itemize}
        \item 默认情况下，输出路径和源文件的结构相匹配。例如源文件中CMakeLists.txt在subdir1下，则默认的可执行文件也在编译路径的subdir1下
        \item 如果执行\verb|add_executable|的CMake是在子目录下，那么默认生成的可执行文件也在相应的子目录内
        \item 可执行文件的路径和名称可以通过\verb|RUNTIME_OUTPUT_DIRECTORY|和\verb|OUTPUT_NAME|两个property来控制。这两个属性默认为空
        \item \verb|RUNTIME_OUTPUT_DIRECTORY|属性，控制输出的路径
        \item \verb|OUTPUT_NAME|属性，控制输出的名称
    \end{itemize}
\end{enumerate}

\paragraph{参数}

\begin{enumerate}
    \item \verb|WIN32|
    
    If \verb|WIN32| is given the property \verb|WIN32_EXECUTABLE| will be set on the target created. See documentation of that target property for details.
    
    \item \verb|MACOSX_BUNDLE|
    
    If \verb|MACOSX_BUNDLE| is given the corresponding property will be set on the created target. See documentation of the \verb|MACOSX_BUNDLE| target property for details.
    
    \item \verb|EXCLUDE_FROM_ALL|
    
    \begin{itemize}
        \item 当函数中使用了\verb|EXCLUDE_FROM_ALL|参数之后，这个target就不会在\verb|make|的时候做任何操作，等于说是暂时屏蔽掉了。
        
        举例\verb|add_executable(${PROJECT_NAME} EXCLUDE_FROM_ALL src/main.cpp)|
    \end{itemize}
    
\end{enumerate}

\section{其他命令}
\subsection{find\_package()}
\verb|find_package(OpenCV)|，查找一个名为\verb|OpenCV|的包，找不到不报错。

\verb|find_package(OpenCV REQUIRED)|找不到会报错。

\verb|find_package(OpenCV REQUIRED COMPONENTS core vedioio)|，找不到会报错，而且必须有\verb|OpenCV::core|和\verb|OpenCV::videoio|这两个组件，否则也会报错

如果\verb|COMPONENTS|更换成\verb|OPTIONAL_COMPONENTS|，则没有这两个组件也并不会报错。

运行\verb|find_package(OpenCV)|命令实际上是查找一个名为\verb|OpenCVConfig.cmake|的文件，这个文件一般存储在\verb|CMAKE_MODULE_PATH|路径中。

在\verb|*.cmake|文件中记录了相关的\verb|*.so|，\verb|*.h|文件的所在位置。

\subsubsection{两种模式}
\verb|find_package()|命令具有2种模式
\begin{enumerate}
    \item 模块模式(module mode)    
    \item 配置模式(config mode)
\end{enumerate}

如果模块模式的搜索失败，则会转换到配置模式下进行。

模块模式是基础工作模式，也是默认会进入的工作模式。

\paragraph{模块模式}
该模式下查找的是 \verb|Find<PackageName>.cmake| 的文件。

cmake 通过 \verb|CMAKE_MODULE_PATH| 设置模块模式的搜索路径
\begin{lstlisting}
# CMAKE_MODULE_PATH 是模块模式的默认搜索路径
# 方式一
set(CMAKE_MODULE_PATH xxx) 
# 方式二
list(APPEND CMAKE_MODULE_PATH 搜索路径)
\end{lstlisting}

\paragraph{配置模式}
该模式下查找的是 \verb|<lowercasePackageName>-config.cmake| 或 \verb|<PackageName>Config.cmake| 的文件。

\verb|xxx-config.cmake| 文件一般是小写开头，\verb|XXXConfig.cmake| 文件一般是大写开头。

cmake 通过 \verb|CMAKE_PREFIX_PATH| 设置配置模式的搜索路径
\begin{lstlisting}
# CMAKE_PREFIX_PATH 是配置模式的搜索路径
# 方式一
set(CMAKE_PREFIX_PATH 搜索路径)
# 方式二
list(APPEND CMAKE_PREFIX_PATH 搜索路径)
\end{lstlisting}

\subsubsection{搜索的路径与搜索结果}
\verb|find_package()| 可以在搜索路径和默认路径(环境变量)中查找指定的库或者模块。无论使用哪种模式，都会设置一个 \verb|<PackageName>_FOUND| 变量来指示是否找到了该package。

对于原生支持Cmake编译和安装的库通常会安装Config模式的配置文件到对应目录，这个配置文件直接配置了头文件库文件的路径以及各种cmake变量供 \verb|find_package()| 使用。

而对于非由cmake编译的项目，我们通常会编写一个\verb|Find<LibraryName>.cmake|，通过脚本来获取头文件、库文件等信息。通常，原生支持cmake的项目库安装时会拷贝一份\verb|XXXConfig.cmake|到系统目录中，因此在没有显式指定搜索路径时也可以顺利找到。

\subsubsection{官方提供的Find*.cmake文件}
cmake官方为我们预定义了许多寻找依赖包的Module

他们存储在\verb|path_to_your_cmake/share/cmake-<version>/Modules|目录下。

每个以\verb|Find<LibaryName>.cmake|命名的文件都可以帮我们找到一个包。我们也可以在官方文档中查看到哪些库官方已经为我们定义好了，我们可以直接使用\verb|find_package()|函数进行引用

\subsubsection{为自己的项目编写Find<LibraryName>.cmake文件}
本质上\verb|Find*.cmake|中只是一个寻找库路径的脚本。

里面会定义一些需要到的变量，正是因为其他人运行了这个脚本，所以他们就能访问到这里面预先定义好的变量。

一般会定义的变量列在了下面。需注意这并不是固定的，大概是约定俗成的。这为我们自己编写\verb|Findxxx.cmake|提出了一定的要求，不能乱写。

\begin{enumerate}
    \item \verb|<LibaryName>_FOUND|
    \item \verb|<LibaryName>_INCLUDE_DIR|
    \item \verb|<LibaryName>_INCLUDES|
    \item \verb|<LibaryName>_LIBRARY|
    \item \verb|<LibaryName>_LIBRARIES|
\end{enumerate}

\paragraph{示例}
下面给出了一个示例，为库\verb|MyLib|编写的\verb|FindMyLib.cmake|文件
\begin{lstlisting}
# - Try to find MyLib
# Once done, this will define
#
#  MYLIB_FOUND - system has MyLib
#  MYLIB_INCLUDE_DIRS - the MyLib include directories
#  MYLIB_LIBRARIES - link these to use MyLib

# Define the version of MyLib
set(MYLIB_VERSION "1.0.0")

# Find the include directory
find_path(MYLIB_INCLUDE_DIR
NAMES mylib.h
PATHS
/usr/include
/usr/local/include
$ENV{MYLIB_ROOT}/include
DOC "The directory where mylib.h resides"
)

# Find the library
find_library(MYLIB_LIBRARY
NAMES mylib
PATHS
/usr/lib
/usr/local/lib
$ENV{MYLIB_ROOT}/lib
DOC "The MyLib library"
)

# Handle the QUIETLY and REQUIRED arguments and set MYLIB_FOUND to TRUE if all listed variables are TRUE
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(MyLib DEFAULT_MSG
MYLIB_LIBRARY MYLIB_INCLUDE_DIR
)

# Set the include dirs and libraries
if(MYLIB_FOUND)
set(MYLIB_LIBRARIES ${MYLIB_LIBRARY})
set(MYLIB_INCLUDE_DIRS ${MYLIB_INCLUDE_DIR})
endif()

# Mark as advanced
mark_as_advanced(MYLIB_INCLUDE_DIR MYLIB_LIBRARY)
\end{lstlisting}

\subsubsection{其他内置变量}
\begin{itemize}
    \item \verb|<PackageName>_FOUND|，可以判断是否找到对应的包或者模块
    \item \verb|<PackageName>_INCLUDE_DIR|，表示头文件目录（前提是包被找到才会被自动设置）
    \item \verb|<PackageName>_LIBRARIES|，表示库文件
\end{itemize}

注意，\verb|${OpenCV_LIBRARIES}|只是库名，不代表库目录

\section{实现交叉编译，编写toolchain.cmake文件}
\paragraph{交叉编译的含义}
交叉编译，是要在宿主主机host上编译生成在目标主机target上运行的程序，涉及到头文件、库、编译器、环境变量等内容的切换与匹配。

cmake支持使用\verb|xxx.toolchain.cmake|文件来进行交叉编译工具链的配置，其中\verb|xxx|是目标主机target上所运行系统的名称。

使用独立的 \verb|xxx.toolchain.cmake| 来设定某一个平台特定的参数，可以避免侵入式的修改 \verb|CMakeLists.txt|，保持了它的清爽，可维护性和扩展性也比较好。

\paragraph{使用toolchain.cmake文件}
通过参数\verb|-D CMAKE_TOOLCHAIN_FILE=/path/to/xxx.toolchain.cmake|来让cmake使用某个\verb|xxx.toolchain.cmake|文件

\paragraph{一个典型的toolchain.cmake文件的实例}
下面给出了一个典型的toolchain.cmake文件的实例，可见里面基本由大量的\verb|set()|函数组成，设置一些cmake内定变量的值。
\begin{lstlisting}
# 设置 C 和 C++ 编译器
set(CMAKE_C_COMPILER /path/to/your/arm-linux-gnueabihf-gcc)
set(CMAKE_CXX_COMPILER /path/to/your/arm-linux-gnueabihf-g++)

# 设置目标系统信息
set(CMAKE_SYSTEM_NAME Linux)
set(CMAKE_SYSTEM_PROCESSOR arm)

# 指定交叉编译
set(CMAKE_CROSSCOMPILING 1)

# 查找程序时使用目标环境
set(CMAKE_FIND_ROOT_PATH /path/to/your/target/rootfs)

# 在查找库和头文件时只搜索目标环境
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)

# 其他可选设置
# set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wextra")
# set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11")

# 如果有额外的链接选项
# set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--start-group -lm -Wl,--end-group")
\end{lstlisting}

\paragraph{CMAKE\_C\_COMPILER和CMAKE\_CXX\_COMPILER}
这两个内定变量用于指定编译器的路径。当我使用某个\verb|xxx.toolchain.cmake|文件，我就会用这个\verb|xxx.toolchain.cmake|文件中所设置的编译器来进行编译，而不是当前设备环境变量中存储的默认的编译器来进行编译。

\paragraph{CMAKE\_SYSTEM\_PROCESSOR}
\verb|CMAKE_SYSTEM_PROCESSOR|，用于指定目标系统的处理器架构，例如x86\_64、arm、aarch64等。

如果不显式地设置\verb|CMAKE_SYSTEM_PROCESSOR|，那么cmake就会使用宿主主机host里面的设置。cmake官方文档里说，在非交叉编译的情况下，\verb|CMAKE_SYSTEM_PROCESSOR| 变量的值与 \verb|CMAKE_HOST_SYSTEM_PROCESSOR| 变量相同。这显然是与交叉编译的目的相违背了。所以一定要通过\verb|CMAKE_SYSTEM_PROCESSOR|设置正确的目标主机target的处理器架构。

\paragraph{CMAKE\_SYSTEM\_NAME}
\verb|CMAKE_SYSTEM_NAME|，用于指定目标系统的名称，例如Linux、Windows、Darwin(macOS)、FreeBSD等。

\paragraph{CMAKE\_CROSSCOMPILING}
\verb|CMAKE_CROSSCOMPILING|，用于表示当前编译过程是否为交叉编译。在交叉编译模式下，cmake 会改变其查找库和头文件的行为。在交叉编译时，一些针对主机系统的检查(如尝试运行可执行文件以确定特性)会被跳过，因为这些操作在目标平台上可能无法执行。如果不显式地设置\verb|CMAKE_CROSSCOMPILING|，cmake也可能通过其他途径来获知此时正在进行交叉编译，并且将\verb|CMAKE_CROSSCOMPILING|设置为\verb|TRUE|

\paragraph{CMAKE\_FIND\_ROOT\_PATH}
这个内建变量用于设置在交叉编译时查找库、头文件和其他资源的根路径。在交叉编译时，就不能在宿主主机host中查找比如\verb|/usr/include|这样的目录了，就需要去我们安装了目标主机target编译器的路径下去搜索相关资源。

\section{pkg\_check\_modules}
\verb|pkg_check_modules| 主要通过 \verb|*.pc| 文件引入整个库，该命令会解析指定的 \verb|*.pc| 文件，并将所需的编译和链接信息提取到 CMake 变量中，以便后续使用。

\paragraph{pkg\_check\_modules的检索目录}
默认路径一般为\verb|/usr/lib/pkgconfig|、\verb|/usr/local/lib/pkgconfig|

如果还有其他路，可以通过环境变量\verb|PKG_CONFIG_PATH|来设置
\begin{lstlisting}
# ffmpeg 的pc文件位置：${PROJECT_SOURCE_DIR}/3rdparty/ffmpeg/lib/pkgconfig
# 系统环境变量：PKG_CONFIG_PATH
# 格式： set(ENV{PKG_CONFIG_PATH} "$ENV{PKG_CONFIG_PATH}:<pc文件位置>")
set(ENV{PKG_CONFIG_PATH} "$ENV{PKG_CONFIG_PATH}:${PROJECT_SOURCE_DIR}/3rdparty/ffmpeg/lib/pkgconfig")
\end{lstlisting}

\subsection{pkg\_check\_modules的语法}
基本语法如下。和\verb|find_package|类似
\begin{lstlisting}
pkg_check_modules(<PREFIX> REQUIRED|QUIET|OPTIONAL [IMPORTED_TARGETS] <modules>...)
\end{lstlisting}

\begin{enumerate}
    \item \verb|<PREFIX>|，相当于为后续要引入的库起一个别名，这个别名会被用于多个地方，最典型的就是cmake变量，头文件路径会被保存到  \verb|XXX_INCLUDE_DIRS|，库文件路径会被保存到\verb|XXX_LIB|，而这里的\verb|<PREFIX>|则用于替换变量中的\verb|XXX|部分
    \item \verb|REQUIRED/QUIT/OPTIONAL|
    \begin{itemize}
        \item \verb|REQUIRED|表示该软件包是必需的，如果找不到将会导致配置错误
        \item \verb|QUIT|表示即使找不到也不会产生错误，只是输出一条警告
        \item \verb|OPTIONAL|表示查找不到软件包也不会产生错误，且不会输出任何信息。
    \end{itemize}
    \item \verb|[IMPORTED_TARGETS]|，可选参数，指定是否应该为该软件包创建\verb|IMPORTED|目标。
    \item \verb|<modules>...|，要查询的软件包名称，通常是在系统上安装的软件包的名字。假设要查找 \verb|libavcodec.pc| 文件，那么这里填入的就是 \verb|libavcodec|。\verb|pkg_check_modules|一次可以查找多个\verb|*.pc|文件。
\end{enumerate}

\subsection{代码示例}
下面介绍两种，一种是使用了 \verb|IMPORTED_TARGETS| 选项，一种是不使用 \verb|IMPORTED_TARGETS| 选项。二者的区别在于，找到目标后链接到目标文件的方式不同，寻找 \verb|*.pc| 文件的方式是几乎一样的。

\subsubsection{使用IMPORTED\_TARGETS的方式}
\begin{lstlisting}
# 添加检索路径
set(ENV{PKG_CONFIG_PATH} "$ENV{PKG_CONFIG_PATH}:${PROJECT_SOURCE_DIR}/3rdparty/ffmpeg/lib/pkgconfig")
# 查找 pc 文件
find_package(PkgConfig REQUIRED)
pkg_check_modules(Avfilter REQUIRED IMPORTED_TARGET libavfilter) # 使用 IMPORTED_TARGETS
if(Avfilter_FOUND)
    message(STATUS "found avfilter")
    message(STATUS "    - avfilter include directories: ${Avfilter_INCLUDE_DIRS}")
    message(STATUS "    - avfilter libraries directories: ${Avfilter_LIBRARY_DIRS}")
    message(STATUS "    - avfilter libraries: ${Avfilter_LIBRARIES}")
    include_directories(${Avfilter_INCLUDE_DIRS})
    link_directories(${Avfilter_LIBRARY_DIRS})
else()
    message(FATAL_ERROR "avfilter not found")
endif()

# 链接到目标文件
add_executable(${PROJECT_NAME} ${ALL_SRCS})
target_link_libraries(${PROJECT_NAME} PRIVATE
    PkgConfig::Avfilter       
) # 这里的Avfilter是pkg_check_modules的第一个参数
\end{lstlisting}

\subsubsection{不使用IMPORTED\_TARGETS的方式}
\begin{lstlisting}
# 添加检索路径
set(ENV{PKG_CONFIG_PATH} "$ENV{PKG_CONFIG_PATH}:${PROJECT_SOURCE_DIR}/3rdparty/ffmpeg/lib/pkgconfig")
# 查找 pc 文件
find_package(PkgConfig REQUIRED)
pkg_check_modules(Avfilter REQUIRED libavfilter) # 不使用 IMPORTED_TARGETS
if(Avfilter_FOUND)
    message(STATUS "found avfilter")
    message(STATUS "    - avfilter include directories: ${Avfilter_INCLUDE_DIRS}")
    message(STATUS "    - avfilter libraries directories: ${Avfilter_LIBRARY_DIRS}")
    message(STATUS "    - avfilter libraries: ${Avfilter_LIBRARIES}")
    include_directories(${Avfilter_INCLUDE_DIRS})
    link_directories(${Avfilter_LIBRARY_DIRS})
else()
    message(FATAL_ERROR "avfilter not found")
endif()

# 链接到目标文件
add_executable(${PROJECT_NAME} ${ALL_SRCS})
target_link_libraries(${PROJECT_NAME} PRIVATE
    ${Avfilter_LIBRARIES}
)
\end{lstlisting}

可见，使用\verb|IMPORTED_TARGETS|选项后，再将搜索结果添加到动态链接库中，使用的是\verb|PkgConfig::Avfilter|，而不使用\verb|IMPORTED_TARGETS|选项，添加动态链接库时使用的是\verb|${Avfilter_LIBRARIES}|。

\chapter{docker - 容器化技术}
\minitoc
\section{基本概念}
docker是一个用于构建build、运行run、发送share应用程序的平台。

\paragraph{docker和虚拟机的区别}
虚拟化依赖于虚拟化hypervision技术，将一个物理主机划分成多个逻辑主机。

\paragraph{docker不是唯一的容器化工具}
docker和容器的联系和区别。docker是容器的一种解决方案。

\subsection{docker的组成}
客户端client，服务端docker host，仓库registry，镜像，容器container。

\paragraph{镜像}
镜像是一个只读的模板，用来创建容器。

\paragraph{容器}
容器是一个运行实例，提供可移植的运行环境。仓库是存储docker镜像的，比如dockerhub。

\paragraph{docker deamon}
docker是一个c/s结构，docker守护进程(docker deamon)运行在主机上，通过socket连接从客户端访问，守护进程从客户端接受命令并管理运行在主机上的容器。

docker client和docker deamon通过socket或者restful API进行通信。docker deamon是服务端的守护进程，收到client的各种请求，处理后再回复给client。

docker deamon提供docker server，用来与docker client进行通信，接受其请求。

\paragraph{docker engine与job}
docker engine执行docker内部的一系列工作，每一项工作都是以一个job的形式存在。

job的运行过程中，当需要容器镜像时，则从docker registry中下载镜像，并通过镜像管理驱动graph driver将镜像以graph的形式存储。

当需要为docker创建网络环境时，通过网络管理驱动network driver创建并配置docker容器网络环境。

当需要限制docker容器运行资源或执行用户指令等操作时，则通过exec driver来完成。

libcontainer是一个独立的容器管理包，network driver以及exec driver都是通过libcontainer来实现具体对容器的操作。

\subsection{其他概念}
\paragraph{dockerhub}
dockerhub，国内使用镜像代理来加速。

playwithdocker网站，在线docker环境。

\paragraph{dockerfile}
容器化和Dockerfile。创建Dockerfile，使用Dockerfile构建镜像，使用镜像创建和运行容器。Dockerfile是一个文本文件，里面包含docker命令。

Dockerfile的名称是固定的，必须写为这个名字，而且大小写也要一致。

使用\verb|docker build|命令基于Dockerfile来构建镜像，比如\verb|docker build -t [imageName] .|，其中\verb|[imageName]|给出的是镜像的名称。

%docker compose
\section{容器}
后台运行的守护容器，必须要有一个一直在前台运行的进程，比如top，tail等，如果没有的话，容器启动后会迅速退出。这是docker的设计机制。

导入和导出容器。export，将容器里的内容作为一个tar归档文件保存。import，从tar包中创建一个新的文件系统再导入镜像。

<<<<<<< Updated upstream
\paragraph{容器中的更改}
容器中的修改，在关闭后就会消失。从相同镜像启动的新的容器，并不会保留原来的更改。如果想要保留这个更改，则需要建立一个新的镜像，也就是提交一个\verb|docker commit|，原理上类似于git的\verb|commit|。每个\verb|commit|只记录了与上一次\verb|commit|之间的差异，最新的\verb|commit|以层累方式存储了所有的变动。

\paragraph{容器使用主机CPU的核数}
默认情况下，容器可以使用主机所有的CPU核来进行计算。

通过\verb|--cpus|、\verb|--cpuset-cpus| 或 \verb|--cpu-shares| 选项，可以对容器使用主机CPU核数进行配置，使用多少个核，使用哪个核，按份额分配核，等详细配置。

在容器内部，可以使用\verb|cat /proc/cpuinfo|来查看当前使用多少个核。
=======
\paragraph{导出和导入容器}
导出和导入容器，使用的命令是\verb|docker export|和\verb|docker import|。
\begin{lstlisting}
sudo docker export -o ./webapp.tar webapp

sudo docker import ./webapp.tar webapp:1.0
\end{lstlisting}
>>>>>>> Stashed changes

\section{镜像}
镜像的分层概念。什么是镜像。根据镜像才能生成容器。联合文件系统UnionFS。UnionFS是docker镜像的基础。bootfs，rootfs。docker镜像的最底层是引导文件系统bootfs。

如果有多个镜像，都是从1个基础镜像继承而来，那么主机上只需要保存一份基础镜像即可，内存上也只需加载一份基础镜像即可为所有的容器服务了。

docker的镜像层都是只读的，容器层都是可写的。当容器启动后，一个新的可写层，被加载到镜像顶部。

docker commit，提交新的镜像。新镜像是在旧镜像上堆叠形成的。

镜像commit之间的关系，类似于git的commit树上的关系，git只存储他们之间的差异，而并不是完整地保存每一份副本。因为想要获取一份完整副本的话，只需要重复所有已记录的改动即可。

\paragraph{导出和导入镜像}
使用\verb|docker save|命令的\verb|-o|选项，来将镜像生成为一个文件，再在需要的时候使用与之对应的\verb|docker load|命令来读取一个文件。

导入的镜像会延用原有的镜像名称。

在下面的代码示例中，我们生成了一个文件\verb|webapp-1.0.tar|来存储镜像\verb|webapp:1.0|。
\begin{lstlisting}
sudo docker save -o ./webapp-1.0.tar webapp:1.0

sudo docker load -i webapp-1.0.tar
\end{lstlisting}

\section{常用命令}
\subsubsection{帮助与启动命令}
表\ref{docker帮助与启动命令}列出了常见的帮助与启动相关的命令。
\begin{table}[ht]
    \centering
    \caption{docker帮助与启动命令} \label{docker帮助与启动命令}
    \begin{tabular}{|p{15em}|p{30em}|}
        \hline
        命令 & 功能 \\ \hline
        \verb|systemctl start docker|   & 启动docker \\ \hline
        \verb|systemctl stop docker|    & 停止docker \\ \hline
        \verb|systemctl restart docker| & 重启docker \\ \hline
        \verb|systemctl status docker|  & 查看docker状态 \\ \hline
        \verb|systemctl enable docker|  & 开机启动 \\ \hline
        \verb|docker info|              & 查看docker信息 \\ \hline
        \verb|docker –-help|            & 查看帮助文档 \\ \hline
        \verb|docker [command] –help|   & 查看某个命令的帮助文档 \\ \hline
    \end{tabular}
\end{table}

\subsubsection{镜像命令}
表\ref{docker镜像命令}列出了常见的操作镜像相关的命令。
\begin{table}[ht]
    \centering
    \caption{docker镜像命令} \label{docker镜像命令}
    \begin{tabular}{|p{15em}|p{30em}|}
        \hline
        命令 & 功能 \\ \hline
        \verb|docker images| & 查看本机上面的所有可用镜像 \\ \hline
        \verb|docker search| & 查找容器(包括网络上的)，--limit N，只列出N个镜像 \\ \hline
        \verb|docker system df| & 查看某个镜像所占的空间 \\ \hline
    \end{tabular}
\end{table}

\paragraph{虚悬镜像(dangling image)}
虚悬镜像(dangling image)，repo和tag都是<none>的镜像，如果遇到，需要删除，否则占用空间。

有镜像才能创建容器。

\paragraph{从容器创建新镜像}
具体参数的含义见表\ref{docker commit命令的选项与功能}。
\begin{lstlisting}
docker commit [OPTIONS] CONTAINER [REPOSITORY[:TAG]]
\end{lstlisting}
\begin{table}[ht]
    \centering
    \caption{选项与功能}\label{docker commit命令的选项与功能}
    \begin{tabular}{|p{15em}|p{30em}|}
        \hline
        选项 & 功能 \\ \hline
        \verb|-a [authorName]| & 设置提交的镜像作者 \\ \hline
        \verb|-c| & 使用Dockerfile指令来创建镜像 \\ \hline
        \verb|-m| & 提交时的说明文字 \\ \hline
        \verb|-p| & 在commit时，将容器暂停 \\ \hline
    \end{tabular}
\end{table}

\paragraph{查找镜像}
\verb|docker search imageName|

\subsubsection{容器命令}
表\ref{docker容器命令}列出了常见的操作容器相关的命令。
\begin{table}[ht]
    \centering
    \caption{docker镜像命令} \label{docker容器命令}
    \begin{tabular}{|p{15em}|p{30em}|}
        \hline
        命令 & 功能 \\ \hline
        \verb|docker run image| & 启动容器 \\ \hline
        \verb|exit| & 退出终端 \\ \hline
        \verb|docker ps| & 显示所有正在运行的容器实例。具体参数请见表\ref{docker ps命令的选项与功能} \\ \hline
        \verb|docker kill| & 强制停止容器 \\ \hline
        \verb|docker rm|或者\verb|docker rmi| & 删除容器 \\ \hline
    \end{tabular}
\end{table}

\paragraph{启动容器}
启动容器主要通过\verb|docker run image|命令，具体参数的含义见表\ref{docker run命令的选项与功能}。
\begin{lstlisting}
docker run [options] image [command] [arg...]
\end{lstlisting}
\begin{table}[ht]
    \centering
    \caption{docker run命令的选项与功能}\label{docker run命令的选项与功能}
    \begin{tabular}{|p{15em}|p{30em}|}
        \hline
        选项 & 功能 \\ \hline
        \verb|--name [newName]| & 为容器指定新名称。如果没有起名字，系统会随机分配。 \\ \hline
        \verb|-d| & 后台运行(守护式容器)并返回容器id。 \\ \hline
        \verb|-i| & 以交互模式运行容器，通常与\verb|-t|同时使用。 \\ \hline
        \verb|-t| & 为容器重新分配一个伪终端。 \\ \hline
        \verb|-p [主机端口]:[容器端口]| & 端口映射，人工指定。\verb|-P|，系统随机指定。 \\ \hline
        \verb|[arg...]| & 指定伪终端所使用的shell，比如在\verb|[arg...]|中传入\verb|/usr/bash|。 \\ \hline
        \verb|-e "name=value"| & 设置环境变量 \\ \hline
        \verb|-v| & 目录映射，容器目录挂载到宿主机目录，格式:\verb|<host目录>:<容器目录>| \\ \hline
    \end{tabular}
\end{table}

\paragraph{退出容器，强制停止容器}
两种退出容器的方式。使用\verb|exit|，容器会停止。但是使用\verb|ctrl+p+q|退出，容器不会停止。

\verb|docker kill|可以强制停止容器。

\paragraph{显示正在运行的容器}
\begin{table}[ht]
    \centering
    \caption{选项与功能}\label{docker ps命令的选项与功能}
    \begin{tabular}{|p{15em}|p{30em}|}
        \hline
        选项 & 功能 \\ \hline
        \verb|-n [数字]| & 只列举指定个数的容器实例。 \\ \hline
        \verb|-q| & 只显示容器编号。如果没有启动成功，\verb|docker ps|里面是不会显示的。 \\ \hline
    \end{tabular}
\end{table}

\paragraph{删除容器}
\verb|docker rm|不会删除正在运行的容器。
\begin{lstlisting}
docker rm [OPTIONS] CONTAINER [CONTAINER...]
\end{lstlisting}
\begin{table}[ht]
    \centering
    \caption{选项与功能}\label{docker rm命令的选项与功能}
    \begin{tabular}{|p{15em}|p{30em}|}
        \hline
        选项 & 功能 \\ \hline
        \verb|CONTAINER| & 接上一个或者多个容器的名称 \\ \hline
        \verb|-f, --force| & 强制删除正在运行的容器，会使用\verb|SIGKILL|信号停止该容器的运行。 \\ \hline
        \verb|-l, --link| & 删除指定的连接，而不是容器本身。 \\ \hline
        \verb|-v, --volumes| & 删除容器挂载的卷。 \\ \hline
    \end{tabular}
\end{table}

\verb|docker prune|命令也可以用来删除。

\verb|docker image prune|删除所有未被 tag 标记和未被容器使用的镜像

\verb|docker container prune|删除所有停止运行的容器

\verb|docker network prune|删除所有网络

\verb|docker system prune|删除 docker 所有资源

\paragraph{让容器执行命令}
\verb|docker exec|，在运行的容器中执行命令
\begin{lstlisting}
docker exec [OPTIONS] CONTAINER COMMAND [ARG...]
\end{lstlisting}
\begin{table}[ht]
    \centering
    \caption{选项与功能}\label{docker exec命令的选项与功能}
    \begin{tabular}{|p{15em}|p{30em}|}
        \hline
        选项 & 功能 \\ \hline
        \verb|-d| & 分离模式，在后台运行 \\ \hline
        \verb|-i| & 即使没有附加也保持STDIN 打开 \\ \hline
        \verb|-t| & 分配一个伪终端 \\ \hline
    \end{tabular}
\end{table}

\paragraph{连接到容器}
\verb|docker attach|，连接到正在运行中的容器。

要 \verb|attach| 上去的容器必须正在运行，可以同时连接上同一个container来共享屏幕(与screen命令的 \verb|attach| 类似)。
\begin{lstlisting}
docker attach [OPTIONS] CONTAINER
\end{lstlisting}

\paragraph{attach与exec的区别}
\verb|docker exec –it [容器ID] bash|和\verb|docker attach [容器ID]|有什么区别？

\verb|attach|直接进入容器启动命令时的终端，不会启动新的进程，用\verb|exit|退出，会导致容器的停止。而\verb|exec|是在容器中打开新的终端，并且可以启动新的进程，用\verb|exit|退出并不会导致容器的停止。更推荐使用\verb|docker exec|，有容错。

\paragraph{从容器到主机拷贝文件}
\verb|docker cp 容器ID:容器内路径 主机路径|

\paragraph{检查容器变更}
\verb|docker diff|


\part{其他内容}
\chapter{待补充内容}
\begin{enumerate}
    \item fork()和execve()
    \item qemu
    \item initrd
    \item pmap命令，查看进程的地址空间
    \item /proc/[进程号]/maps文件
    \item perf
    \item systemctl, uname命令
\end{enumerate}

\end{document}